forked from LiveCarta/LiveCartaWP
Changed source root directory
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Ajax;
|
||||
|
||||
abstract class AbstractAjaxService
|
||||
{
|
||||
/**
|
||||
* Init ajax calls
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function init();
|
||||
|
||||
/**
|
||||
* Add ajax action
|
||||
*
|
||||
* @param string $tag ajax tag name
|
||||
* @param string $methodName method name
|
||||
*
|
||||
* @return bool Always returns true
|
||||
*/
|
||||
protected function addAjaxCall($tag, $methodName)
|
||||
{
|
||||
return add_action($tag, array($this, $methodName));
|
||||
}
|
||||
}
|
||||
133
html/wp-content/plugins/duplicator/src/Ajax/AjaxWrapper.php
Normal file
133
html/wp-content/plugins/duplicator/src/Ajax/AjaxWrapper.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Ajax;
|
||||
|
||||
use DUP_Handler;
|
||||
use DUP_Log;
|
||||
use DUP_Util;
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
use Exception;
|
||||
|
||||
class AjaxWrapper
|
||||
{
|
||||
/**
|
||||
* This function wrap a callback and return always a json well formatted output.
|
||||
*
|
||||
* check nonce and capability if passed and return a json with this format
|
||||
* [
|
||||
* success : bool
|
||||
* data : [
|
||||
* funcData : mixed // callback return data
|
||||
* message : string // a message for jvascript func (for example an exception message)
|
||||
* output : string // all normal output wrapped between ob_start and ob_get_clean
|
||||
* // if $errorUnespectedOutput is true and output isn't empty the json return an error
|
||||
* ]
|
||||
* ]
|
||||
*
|
||||
* @param callable $callback callback function
|
||||
* @param string $nonceaction if action is null don't verify nonce
|
||||
* @param string $nonce nonce string
|
||||
* @param string $capability if capability is null don't verify capability
|
||||
* @param bool $errorUnespectedOutput if true thorw exception with unespected optput
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function json(
|
||||
$callback,
|
||||
$nonceaction = null,
|
||||
$nonce = null,
|
||||
$capability = null,
|
||||
$errorUnespectedOutput = true
|
||||
) {
|
||||
$error = false;
|
||||
|
||||
$result = array(
|
||||
'funcData' => null,
|
||||
'output' => '',
|
||||
'message' => ''
|
||||
);
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
DUP_Handler::init_error_handler();
|
||||
$nonce = SnapUtil::sanitizeNSCharsNewline($nonce);
|
||||
if (is_null($nonceaction) || !wp_verify_nonce($nonce, $nonceaction)) {
|
||||
DUP_Log::trace('Security issue');
|
||||
throw new Exception('Security issue');
|
||||
}
|
||||
if (!is_null($capability)) {
|
||||
DUP_Util::hasCapability($capability, DUP_Util::SECURE_ISSUE_THROW);
|
||||
}
|
||||
|
||||
// execute ajax function
|
||||
$result['funcData'] = call_user_func($callback);
|
||||
} catch (Exception $e) {
|
||||
$error = true;
|
||||
$result['message'] = $e->getMessage();
|
||||
}
|
||||
|
||||
$result['output'] = ob_get_clean();
|
||||
if ($errorUnespectedOutput && !empty($result['output'])) {
|
||||
$error = true;
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
wp_send_json_error($result);
|
||||
} else {
|
||||
wp_send_json_success($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function wrap a callback and start a chunked file download.
|
||||
* The callback must return a file path.
|
||||
*
|
||||
* @param callable():false|array{path:string,name:string} $callback Callback function that return a file path for download or false on error
|
||||
* @param string $nonceaction if action is null don't verify nonce
|
||||
* @param string $nonce nonce string
|
||||
* @param bool $errorUnespectedOutput if true thorw exception with unespected optput
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
public static function fileDownload(
|
||||
$callback,
|
||||
$nonceaction = null,
|
||||
$nonce = null,
|
||||
$errorUnespectedOutput = true
|
||||
) {
|
||||
ob_start();
|
||||
try {
|
||||
DUP_Handler::init_error_handler();
|
||||
$nonce = SnapUtil::sanitizeNSCharsNewline($nonce);
|
||||
if (!is_null($nonceaction) && !wp_verify_nonce($nonce, $nonceaction)) {
|
||||
DUP_Log::trace('Security issue');
|
||||
throw new Exception('Security issue');
|
||||
}
|
||||
|
||||
// execute ajax function
|
||||
if (($fileInfo = call_user_func($callback)) === false) {
|
||||
throw new Exception('Error generating file');
|
||||
}
|
||||
|
||||
if (!@file_exists($fileInfo['path'])) {
|
||||
throw new Exception('File ' . $fileInfo['path'] . ' not found');
|
||||
}
|
||||
|
||||
$result['output'] = ob_get_clean();
|
||||
if ($errorUnespectedOutput && !empty($result['output'])) {
|
||||
throw new Exception('Unespected output');
|
||||
}
|
||||
|
||||
SnapIO::serveFileForDownload($fileInfo['path'], $fileInfo['name'], DUPLICATOR_BUFFER_READ_WRITE_SIZE);
|
||||
} catch (Exception $e) {
|
||||
DUP_Log::trace($e->getMessage());
|
||||
SnapIO::serverError500();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Ajax;
|
||||
|
||||
use DUP_Package;
|
||||
use Duplicator\Ajax\AjaxWrapper;
|
||||
use Duplicator\Views\DashboardWidget;
|
||||
|
||||
class ServicesDashboard extends AbstractAjaxService
|
||||
{
|
||||
/**
|
||||
* Init ajax calls
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->addAjaxCall('wp_ajax_duplicator_dashboad_widget_info', 'dashboardWidgetInfo');
|
||||
$this->addAjaxCall('wp_ajax_duplicator_dismiss_recommended_plugin', 'dismissRecommendedPlugin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set recovery callback
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function dashboardWidgetInfoCallback()
|
||||
{
|
||||
$result = array(
|
||||
'isRunning' => DUP_Package::isPackageRunning(),
|
||||
'lastBackupInfo' => DashboardWidget::getLastBackupString()
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set recovery action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dashboardWidgetInfo()
|
||||
{
|
||||
AjaxWrapper::json(
|
||||
array(__CLASS__, 'dashboardWidgetInfoCallback'),
|
||||
'duplicator_dashboad_widget_info',
|
||||
$_POST['nonce'],
|
||||
'export'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set dismiss recommended callback
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function dismissRecommendedPluginCallback()
|
||||
{
|
||||
return (update_user_meta(get_current_user_id(), DashboardWidget::RECOMMENDED_PLUGIN_DISMISSED_OPT_KEY, true) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set recovery action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dismissRecommendedPlugin()
|
||||
{
|
||||
AjaxWrapper::json(
|
||||
array(__CLASS__, 'dismissRecommendedPluginCallback'),
|
||||
'duplicator_dashboad_widget_dismiss_recommended',
|
||||
$_POST['nonce'],
|
||||
'export'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Ajax;
|
||||
|
||||
use Plugin_Upgrader;
|
||||
use Duplicator\Ajax\AjaxWrapper;
|
||||
use Duplicator\Views\EducationElements;
|
||||
use Exception;
|
||||
use Duplicator\Libs\OneClickUpgrade\UpgraderSkin;
|
||||
use DUP_Log;
|
||||
use DUP_Settings;
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Core\Notifications\Notice;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
|
||||
class ServicesEducation extends AbstractAjaxService
|
||||
{
|
||||
const OPTION_KEY_ONE_CLICK_UPGRADE_OTH = 'duplicator_one_click_upgrade_oth';
|
||||
const AUTH_TOKEN_KEY_OPTION_AUTO_ACTIVE = 'duplicator_pro_auth_token_auto_active';
|
||||
const DUPLICATOR_STORE_URL = "https://duplicator.com";
|
||||
const REMOTE_SUBSCRIBE_URL = 'https://duplicator.com/?lite_email_signup=1';
|
||||
|
||||
/**
|
||||
* Init ajax calls
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->addAjaxCall('wp_ajax_duplicator_settings_callout_cta_dismiss', 'dismissCalloutCTA');
|
||||
$this->addAjaxCall('wp_ajax_duplicator_packages_bottom_bar_dismiss', 'dismissBottomBar');
|
||||
$this->addAjaxCall('wp_ajax_duplicator_email_subscribe', 'setEmailSubscribed');
|
||||
$this->addAjaxCall('wp_ajax_duplicator_generate_connect_oth', 'generateConnectOTH');
|
||||
$this->addAjaxCall('wp_ajax_nopriv_duplicator_lite_run_one_click_upgrade', 'oneClickUpgrade');
|
||||
$this->addAjaxCall('wp_ajax_duplicator_lite_run_one_click_upgrade', 'oneClickUpgrade');
|
||||
$this->addAjaxCall('wp_ajax_duplicator_enable_usage_stats', 'enableUsageStats');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set email subscribed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function setEmailSubscribedCallback()
|
||||
{
|
||||
if (EducationElements::userIsSubscribed()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE);
|
||||
if (is_null($email)) {
|
||||
throw new \Exception('Invalid email');
|
||||
}
|
||||
|
||||
$response = wp_remote_post(self::REMOTE_SUBSCRIBE_URL, array(
|
||||
'method' => 'POST',
|
||||
'timeout' => 45,
|
||||
'body' => array('email' => $email)
|
||||
));
|
||||
|
||||
if (is_wp_error($response) || 200 !== wp_remote_retrieve_response_code($response)) {
|
||||
$error_msg = $response->get_error_code() . ': ' . $response->get_error_message();
|
||||
SnapUtil::errorLog($error_msg);
|
||||
throw new \Exception($error_msg);
|
||||
}
|
||||
|
||||
return (update_user_meta(get_current_user_id(), EducationElements::DUP_EMAIL_SUBSCRIBED_OPT_KEY, true) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set recovery action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setEmailSubscribed()
|
||||
{
|
||||
AjaxWrapper::json(
|
||||
array(__CLASS__, 'setEmailSubscribedCallback'),
|
||||
'duplicator_email_subscribe',
|
||||
$_POST['nonce'],
|
||||
'export'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set dismiss callout CTA callback
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function dismissCalloutCTACallback()
|
||||
{
|
||||
return (update_user_meta(get_current_user_id(), EducationElements::DUP_SETTINGS_FOOTER_CALLOUT_DISMISSED, true) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss callout CTA
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dismissCalloutCTA()
|
||||
{
|
||||
AjaxWrapper::json(
|
||||
array(__CLASS__, 'dismissCalloutCTACallback'),
|
||||
'duplicator_settings_callout_cta_dismiss',
|
||||
$_POST['nonce'],
|
||||
'export'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss bottom bar callback
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function dismissBottomBarCallback()
|
||||
{
|
||||
return (update_user_meta(get_current_user_id(), EducationElements::DUP_PACKAGES_BOTTOM_BAR_DISMISSED, true) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss bottom bar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dismissBottomBar()
|
||||
{
|
||||
AjaxWrapper::json(
|
||||
array(__CLASS__, 'dismissBottomBarCallback'),
|
||||
'duplicator_packages_bottom_bar_dismiss',
|
||||
$_POST['nonce'],
|
||||
'export'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate OTH for connect flow
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function generateConnectOTH()
|
||||
{
|
||||
AjaxWrapper::json(
|
||||
array(__CLASS__, 'generateConnectOTHCallback'),
|
||||
'duplicator_generate_connect_oth',
|
||||
SnapUtil::sanitizeTextInput(INPUT_POST, 'nonce'),
|
||||
'export'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate OTH for connect flow callback
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function generateConnectOTHCallback()
|
||||
{
|
||||
$oth = wp_generate_password(30, false, false);
|
||||
$hashed_oth = self::hashOth($oth);
|
||||
|
||||
// Save HASHED OTH with TTL for security
|
||||
$oth_data = array(
|
||||
'token' => $hashed_oth, // Store hashed OTH for decryption
|
||||
'created_at' => time(),
|
||||
'expires_at' => time() + (10 * MINUTE_IN_SECONDS) // 10 minute expiration
|
||||
);
|
||||
|
||||
delete_option(self::OPTION_KEY_ONE_CLICK_UPGRADE_OTH);
|
||||
$ok = update_option(self::OPTION_KEY_ONE_CLICK_UPGRADE_OTH, $oth_data);
|
||||
|
||||
if (!$ok) {
|
||||
throw new Exception("Problem saving security token.");
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'oth' => $hashed_oth,
|
||||
'php_version' => phpversion(),
|
||||
'wp_version' => get_bloginfo('version'),
|
||||
'redirect_url' => admin_url('admin-ajax.php?action=duplicator_lite_run_one_click_upgrade')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returh hashed OTH
|
||||
*
|
||||
* @param string $oth OTH
|
||||
*
|
||||
* @return string Hashed OTH
|
||||
*/
|
||||
protected static function hashOth($oth)
|
||||
{
|
||||
return hash_hmac('sha512', $oth, wp_salt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt data using OTH-based key.
|
||||
*
|
||||
* @param string $encryptedData Base64 encoded encrypted data
|
||||
* @param string $oth The OTH token
|
||||
*
|
||||
* @return string|false Decrypted data or false on failure
|
||||
*/
|
||||
protected static function decryptData($encryptedData, $oth)
|
||||
{
|
||||
try {
|
||||
$encryption_key = substr(hash('sha256', $oth), 0, 32); // 32-byte key from OTH
|
||||
$iv = substr($oth, 0, 16); // 16-byte IV from OTH
|
||||
|
||||
$encrypted = base64_decode($encryptedData);
|
||||
return openssl_decrypt($encrypted, 'AES-256-CBC', $encryption_key, 0, $iv);
|
||||
} catch (Exception $e) {
|
||||
DUP_Log::trace("ERROR: Decryption failed: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt and parse encrypted package from service.
|
||||
*
|
||||
* @param string $encryptedPackage Base64 encoded encrypted package
|
||||
* @param string $oth The OTH token
|
||||
*
|
||||
* @return array|false Parsed package data or false on failure
|
||||
*/
|
||||
protected static function decryptPackage($encryptedPackage, $oth)
|
||||
{
|
||||
$decrypted = self::decryptData($encryptedPackage, $oth);
|
||||
|
||||
if ($decrypted === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$package = json_decode($decrypted, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
DUP_Log::trace("ERROR: Invalid JSON in decrypted package");
|
||||
return false;
|
||||
}
|
||||
|
||||
return $package;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enable usage stats
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enableUsageStats()
|
||||
{
|
||||
AjaxWrapper::json(
|
||||
array(__CLASS__, 'enableUsageStatsCallback'),
|
||||
'duplicator_enable_usage_stats',
|
||||
SnapUtil::sanitizeTextInput(INPUT_POST, 'nonce'),
|
||||
'manage_options'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable usage stats callback
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enableUsageStatsCallback()
|
||||
{
|
||||
$result = true;
|
||||
if (DUP_Settings::Get('usage_tracking') !== true) {
|
||||
DUP_Settings::setUsageTracking(true);
|
||||
$result = DUP_Settings::Save();
|
||||
}
|
||||
|
||||
return $result && self::setEmailSubscribedCallback();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Accepts encrypted package from remote endpoint, after validating the OTH.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function oneClickUpgrade()
|
||||
{
|
||||
try {
|
||||
// Get encrypted package from service
|
||||
$encryptedPackage = sanitize_text_field($_REQUEST["package"] ?? '');
|
||||
|
||||
if (empty($encryptedPackage)) {
|
||||
DUP_Log::trace("ERROR: No encrypted package received from service.");
|
||||
throw new Exception("No encrypted package received from service");
|
||||
}
|
||||
|
||||
// Get OTH data for validation
|
||||
$oth_data = get_option(self::OPTION_KEY_ONE_CLICK_UPGRADE_OTH);
|
||||
|
||||
if (empty($oth_data) || !is_array($oth_data)) {
|
||||
DUP_Log::trace("ERROR: Invalid OTH data structure.");
|
||||
throw new Exception("Invalid security token");
|
||||
}
|
||||
|
||||
// Check TTL expiration
|
||||
if (time() > $oth_data['expires_at']) {
|
||||
DUP_Log::trace("ERROR: OTH token expired.");
|
||||
delete_option(self::OPTION_KEY_ONE_CLICK_UPGRADE_OTH);
|
||||
throw new Exception("Security token expired");
|
||||
}
|
||||
|
||||
// Decrypt package using OTH
|
||||
$package = self::decryptPackage($encryptedPackage, $oth_data['token']);
|
||||
|
||||
if ($package === false) {
|
||||
DUP_Log::trace("ERROR: Failed to decrypt package from service.");
|
||||
throw new Exception("Invalid encrypted data");
|
||||
}
|
||||
|
||||
// Extract data from decrypted package
|
||||
$download_url = $package['download_url'] ?? '';
|
||||
$auth_token = $package['auth_token'] ?? '';
|
||||
|
||||
if (empty($download_url)) {
|
||||
DUP_Log::trace("ERROR: No download URL in decrypted package.");
|
||||
throw new Exception("No download URL provided");
|
||||
}
|
||||
|
||||
// Delete OTH so it cannot be replayed (single-use)
|
||||
delete_option(self::OPTION_KEY_ONE_CLICK_UPGRADE_OTH);
|
||||
|
||||
// Save authentication token for Pro to use
|
||||
if (!empty($auth_token)) {
|
||||
delete_option(self::AUTH_TOKEN_KEY_OPTION_AUTO_ACTIVE);
|
||||
update_option(self::AUTH_TOKEN_KEY_OPTION_AUTO_ACTIVE, $auth_token);
|
||||
DUP_Log::trace("Authentication token saved for Pro activation.");
|
||||
}
|
||||
|
||||
// Validate download URL format
|
||||
if (!filter_var($download_url, FILTER_VALIDATE_URL)) {
|
||||
DUP_Log::trace("ERROR: Invalid download URL format: " . $download_url);
|
||||
throw new Exception("Invalid download URL format");
|
||||
}
|
||||
|
||||
// Install Pro if not already installed
|
||||
if (!is_dir(WP_PLUGIN_DIR . "/duplicator-pro")) {
|
||||
DUP_Log::trace("Installing Pro using service-provided URL: " . $download_url);
|
||||
|
||||
// Request filesystem credentials
|
||||
$url = esc_url_raw(add_query_arg(array('page' => 'duplicator-settings'), admin_url('admin.php')));
|
||||
$creds = request_filesystem_credentials($url, '', false, false, null);
|
||||
|
||||
if (false === $creds || ! \WP_Filesystem($creds)) {
|
||||
wp_send_json_error(array('message' => 'File system permissions error. Please check permissions and try again.'));
|
||||
}
|
||||
|
||||
// Install the plugin
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
remove_action('upgrader_process_complete', array('Language_Pack_Upgrader', 'async_upgrade'), 20);
|
||||
|
||||
$installer = new Plugin_Upgrader(new UpgraderSkin());
|
||||
$result = $installer->install($download_url);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
DUP_Log::trace("ERROR: Plugin installation failed: " . $result->get_error_message());
|
||||
throw new Exception('Plugin installation failed: ' . $result->get_error_message());
|
||||
}
|
||||
|
||||
wp_cache_flush();
|
||||
$plugin_basename = $installer->plugin_info();
|
||||
|
||||
if ($plugin_basename) {
|
||||
$upgradeDir = dirname($plugin_basename);
|
||||
if ($upgradeDir != "duplicator-pro" && !rename(WP_PLUGIN_DIR . "/" . $upgradeDir, WP_PLUGIN_DIR . "/duplicator-pro")) {
|
||||
throw new Exception('Failed renaming plugin directory');
|
||||
}
|
||||
} else {
|
||||
throw new Exception('Installation of upgrade version failed');
|
||||
}
|
||||
}
|
||||
|
||||
$newFolder = WP_PLUGIN_DIR . "/duplicator-pro";
|
||||
if (!is_dir($newFolder)) {
|
||||
DUP_Log::trace("ERROR: Duplicator Pro folder not found after installation");
|
||||
throw new Exception('Pro plugin installation failed - folder not created');
|
||||
}
|
||||
|
||||
// Deactivate Lite FIRST (critical for avoiding conflicts)
|
||||
deactivate_plugins(DUPLICATOR_PLUGIN_PATH . "/duplicator.php");
|
||||
|
||||
// Create activation URL for Pro
|
||||
$plugin = "duplicator-pro/duplicator-pro.php";
|
||||
$pluginsAdminUrl = is_multisite() ? network_admin_url('plugins.php') : admin_url('plugins.php');
|
||||
$activateProUrl = esc_url_raw(
|
||||
add_query_arg(
|
||||
array(
|
||||
'action' => 'activate',
|
||||
'plugin' => $plugin,
|
||||
'_wpnonce' => wp_create_nonce("activate-plugin_$plugin")
|
||||
),
|
||||
$pluginsAdminUrl
|
||||
)
|
||||
);
|
||||
|
||||
// Redirect to WordPress activation URL
|
||||
DUP_Log::trace("Pro installation successful. Redirecting to activation URL: " . $activateProUrl);
|
||||
wp_safe_redirect($activateProUrl);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
DUP_Log::trace("ERROR in oneClickUpgrade: " . $e->getMessage());
|
||||
|
||||
// Add error notice and redirect to settings page
|
||||
Notice::error(
|
||||
sprintf(__('Upgrade installation failed: %s. Please try again or install manually.', 'duplicator'), $e->getMessage()),
|
||||
'one_click_upgrade_failed'
|
||||
);
|
||||
|
||||
$settingsUrl = ControllersManager::getMenuLink(
|
||||
ControllersManager::SETTINGS_SUBMENU_SLUG,
|
||||
'general'
|
||||
);
|
||||
|
||||
wp_safe_redirect($settingsUrl);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Ajax;
|
||||
|
||||
use Duplicator\Ajax\AjaxWrapper;
|
||||
use Duplicator\Utils\ExtraPlugins\ExtraPluginsMng;
|
||||
|
||||
class ServicesExtraPlugins extends AbstractAjaxService
|
||||
{
|
||||
/**
|
||||
* Init ajax calls
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->addAjaxCall('wp_ajax_duplicator_install_extra_plugin', 'extraPluginInstall');
|
||||
}
|
||||
|
||||
/**
|
||||
* Install and activate or just activate plugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function extraPluginInstallCallback()
|
||||
{
|
||||
$slug = filter_input(INPUT_POST, 'plugin', FILTER_SANITIZE_STRING);
|
||||
$message = '';
|
||||
|
||||
if (!ExtraPluginsMng::getInstance()->install($slug, $message)) {
|
||||
throw new \Exception($message);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addon plugin install action callback
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function extraPluginInstall()
|
||||
{
|
||||
AjaxWrapper::json(
|
||||
array(__CLASS__, 'extraPluginInstallCallback'),
|
||||
'duplicator_install_extra_plugin',
|
||||
$_POST['nonce'],
|
||||
'install_plugins'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Ajax;
|
||||
|
||||
use Duplicator\Ajax\AjaxWrapper;
|
||||
use Duplicator\Core\Notifications\Notifications;
|
||||
|
||||
class ServicesNotifications extends AbstractAjaxService
|
||||
{
|
||||
/**
|
||||
* Init ajax calls
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->addAjaxCall('wp_ajax_duplicator_notification_dismiss', 'setDissmisedNotifications');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss notification
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function dismissNotifications()
|
||||
{
|
||||
$id = sanitize_key($_POST['id']);
|
||||
$type = is_numeric($id) ? 'feed' : 'events';
|
||||
$option = Notifications::getOption();
|
||||
|
||||
$option['dismissed'][] = $id;
|
||||
$option['dismissed'] = array_unique($option['dismissed']);
|
||||
|
||||
// Remove notification.
|
||||
if (!is_array($option[$type]) || empty($option[$type])) {
|
||||
throw new \Exception('Notification type not set.');
|
||||
}
|
||||
|
||||
foreach ($option[$type] as $key => $notification) {
|
||||
if ((string)$notification['id'] === (string)$id) {
|
||||
unset($option[$type][$key]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return update_option(Notifications::DUPLICATOR_NOTIFICATIONS_OPT_KEY, $option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set dismiss notification action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDissmisedNotifications()
|
||||
{
|
||||
AjaxWrapper::json(
|
||||
array(__CLASS__, 'dismissNotifications'),
|
||||
Notifications::DUPLICATOR_NOTIFICATION_NONCE_KEY,
|
||||
$_POST['nonce'],
|
||||
'manage_options'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Ajax;
|
||||
|
||||
use Duplicator\Ajax\AjaxWrapper;
|
||||
use Duplicator\Libs\Snap\SnapURL;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
use Duplicator\Utils\Support\SupportToolkit;
|
||||
|
||||
class ServicesTools extends AbstractAjaxService
|
||||
{
|
||||
/**
|
||||
* Init ajax calls
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->addAjaxCall('wp_ajax_duplicator_download_support_toolkit', 'downloadSupportToolkit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to download diagnostic data
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
public function downloadSupportToolkit()
|
||||
{
|
||||
AjaxWrapper::fileDownload(
|
||||
[
|
||||
__CLASS__,
|
||||
'downloadSupportToolkitCallback',
|
||||
],
|
||||
'duplicator_download_support_toolkit',
|
||||
SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'nonce')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create diagnostic data
|
||||
*
|
||||
* @return false|array{path:string,name:string}
|
||||
*/
|
||||
public static function downloadSupportToolkitCallback()
|
||||
{
|
||||
$domain = SnapURL::wwwRemove(SnapURL::parseUrl(network_home_url(), PHP_URL_HOST));
|
||||
$result = [
|
||||
'path' => SupportToolkit::getToolkit(),
|
||||
'name' => SupportToolkit::SUPPORT_TOOLKIT_PREFIX .
|
||||
substr(sanitize_file_name($domain), 0, 12) . '_' .
|
||||
date('YmdHis') . '.zip',
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Controllers;
|
||||
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
|
||||
class AboutUsController
|
||||
{
|
||||
const ABOUT_US_TAB = 'about-info';
|
||||
const GETTING_STARTED = 'getting-started';
|
||||
const LITE_VS_PRO = 'lite-vs-pro';
|
||||
|
||||
const LITE_ENABLED_FULL = 'full';
|
||||
const LITE_ENABLED_PARTIAL = 'partial';
|
||||
const LITE_ENABLED_NONE = 'none';
|
||||
|
||||
/**
|
||||
* Array containing all the lite vs pro features
|
||||
*
|
||||
* @var string[] $liteVsProfeatures
|
||||
*/
|
||||
public static $liteVsProfeatures = array();
|
||||
|
||||
/**
|
||||
* Enqueue assets
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueues()
|
||||
{
|
||||
self::enqueueScripts();
|
||||
self::enqueueStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueueScripts()
|
||||
{
|
||||
wp_enqueue_script(
|
||||
'duplicator-extra-plugins',
|
||||
DUPLICATOR_PLUGIN_URL . "assets/js/extra-plugins.js",
|
||||
array('jquery'),
|
||||
DUPLICATOR_VERSION,
|
||||
true
|
||||
);
|
||||
wp_localize_script(
|
||||
'duplicator-extra-plugins',
|
||||
'l10nDupExtraPlugins',
|
||||
array(
|
||||
'loading' => esc_html__('Loading...', 'duplicator'),
|
||||
'failure' => esc_html__('Failure', 'duplicator'),
|
||||
'active' => esc_html__('Active', 'duplicator'),
|
||||
'activated' => esc_html__('Activated', 'duplicator'),
|
||||
)
|
||||
);
|
||||
wp_localize_script(
|
||||
'duplicator-extra-plugins',
|
||||
'duplicator_extra_plugins',
|
||||
array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'extra_plugin_install_nonce' => wp_create_nonce('duplicator_install_extra_plugin'),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue styles
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueueStyles()
|
||||
{
|
||||
wp_enqueue_style(
|
||||
'duplicator-about',
|
||||
DUPLICATOR_PLUGIN_URL . "assets/css/about.css",
|
||||
array(),
|
||||
DUPLICATOR_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render welcome screen
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function render()
|
||||
{
|
||||
$levels = ControllersManager::getMenuLevels();
|
||||
TplMng::getInstance()->render(
|
||||
'admin_pages/about_us/tabs',
|
||||
array(
|
||||
'active_tab' => is_null($levels[ControllersManager::QUERY_STRING_MENU_KEY_L2]) ?
|
||||
self::ABOUT_US_TAB : $levels[ControllersManager::QUERY_STRING_MENU_KEY_L2]
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
switch ($levels[ControllersManager::QUERY_STRING_MENU_KEY_L2]) {
|
||||
case self::GETTING_STARTED:
|
||||
TplMng::getInstance()->render('admin_pages/about_us/getting_started/main', array(), true);
|
||||
break;
|
||||
case self::LITE_VS_PRO:
|
||||
TplMng::getInstance()->render('admin_pages/about_us/lite_vs_pro/main', array(), true);
|
||||
break;
|
||||
case self::ABOUT_US_TAB:
|
||||
default:
|
||||
TplMng::getInstance()->render('admin_pages/about_us/about_us/main', array(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lite vs pro features as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getLiteVsProFeatures()
|
||||
{
|
||||
if (!empty(self::$liteVsProfeatures)) {
|
||||
return self::$liteVsProfeatures;
|
||||
}
|
||||
|
||||
self::$liteVsProfeatures = array(
|
||||
array(
|
||||
'title' => __('Backup Files & Database', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_FULL,
|
||||
),
|
||||
array(
|
||||
'title' => __('File & Database Table Filters', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_FULL,
|
||||
),
|
||||
array(
|
||||
'title' => __('Migration Wizard', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_FULL,
|
||||
),
|
||||
array(
|
||||
'title' => __('Overwrite Live Site', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_FULL,
|
||||
),
|
||||
array(
|
||||
'title' => __('Drag & Drop Installs', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_PARTIAL,
|
||||
'lite_text' => __('Classic WordPress-less Installs Only', 'duplicator'),
|
||||
'pro_text' => __(
|
||||
'Drag and Drop migrations and site restores! Simply drag the bundled site archive to the site you wish to overwrite.',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Scheduled Backups', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __(
|
||||
'Ensure that your important data is regularly and consistently backed up, allowing for quick and efficient recovery in case of data loss.',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Recovery Points', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __(
|
||||
'Recovery Points provide protection against mistakes and bad updates by letting you quickly rollback your system to a known, good state.',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Cloud Storage', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __(
|
||||
'Back up to Dropbox, FTP, Google Drive, OneDrive, Amazon S3 or any S3-compatible storage service for safe storage.',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Larger Site Support', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __(
|
||||
'We\'ve developed a new way to package backups especially tailored for larger site. No server timeouts or other restrictions!',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Server-to-Server Import', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __(
|
||||
'Direct Server Transfers allow you to build an archive, then directly transfer it from the source ' .
|
||||
'server to the destination server for a lightning fast migration!',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Multisite support', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __(
|
||||
'Supports multisite network backup & migration. Subsite As Standalone Install, Standalone ' .
|
||||
'Import Into Multisite and Import Subsite Into Multisite',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Installer Branding', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __('Create your own custom-configured WordPress site and "Brand" the installer file with your look and feel.', 'duplicator')
|
||||
),
|
||||
array(
|
||||
'title' => __('Archive Encryption', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __('Protect and secure the archive file with industry-standard AES-256 encryption!', 'duplicator')
|
||||
),
|
||||
array(
|
||||
'title' => __('Advanced Backup Permissions', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __(
|
||||
'Enjoy granular access control to ensure only authorized users can perform these critical functions.',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Enhanced Features', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __(
|
||||
'Enhanced features include: Managed Hosting Support, Shared Database Support, Streamlined Installer, Email Alerts and more...',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Advanced Features', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'pro_text' => __(
|
||||
'Advanced features included: Hourly Schedules, Custom Search & Replace, Migrate Duplicator Settings, Regenerate Salts and Developer Hooks',
|
||||
'duplicator'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'title' => __('Customer Support', 'duplicator'),
|
||||
'lite_enabled' => self::LITE_ENABLED_NONE,
|
||||
'lite_text' => __('Limited Support', 'duplicator'),
|
||||
'pro_text' => __('Priority Support', 'duplicator')
|
||||
)
|
||||
);
|
||||
|
||||
return self::$liteVsProfeatures;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Impost installer page controller
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Controllers;
|
||||
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
use Duplicator\Utils\Email\EmailSummary;
|
||||
|
||||
class EmailSummaryPreviewPageController
|
||||
{
|
||||
/**
|
||||
* Init page
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
if (
|
||||
!isset($_GET['page']) || $_GET['page'] !== EmailSummary::PEVIEW_SLUG
|
||||
|| !is_admin()
|
||||
|| !current_user_can('manage_options')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
TplMng::getInstance()->render('mail/email_summary', array(
|
||||
'packages' => EmailSummary::getInstance()->getPackagesInfo()
|
||||
));
|
||||
die;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Impost installer page controller
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Controllers;
|
||||
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
|
||||
class HelpPageController
|
||||
{
|
||||
const HELP_SLUG = 'duplicator-dynamic-help';
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
if (!ControllersManager::isCurrentPage(self::HELP_SLUG) || !is_admin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tag = SnapUtil::sanitizeInput(INPUT_GET, 'tag', '');
|
||||
TplMng::getInstance()->render(
|
||||
"parts/help/main",
|
||||
[
|
||||
'tag' => $tag,
|
||||
]
|
||||
);
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns link to the help page
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getHelpLink()
|
||||
{
|
||||
return ControllersManager::getMenuLink(self::HELP_SLUG);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Controllers;
|
||||
|
||||
use DUP_UI_Dialog;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
|
||||
class StorageController
|
||||
{
|
||||
/**
|
||||
* Render storages page
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function render()
|
||||
{
|
||||
TplMng::getInstance()->render('mocks/storage/storage', array(
|
||||
'storages' => self::getStoragesData()
|
||||
), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fet storage alert dialog box
|
||||
*
|
||||
* @param string $utm_medium UTM medium for the upsell link
|
||||
*
|
||||
* @return DUP_UI_Dialog
|
||||
*/
|
||||
public static function getDialogBox($utm_medium)
|
||||
{
|
||||
require_once(DUPLICATOR_PLUGIN_PATH . '/classes/ui/class.ui.dialog.php');
|
||||
|
||||
$storageAlert = new DUP_UI_Dialog();
|
||||
$storageAlert->title = __('Advanced Storage', 'duplicator');
|
||||
$storageAlert->height = 600;
|
||||
$storageAlert->width = 550;
|
||||
$storageAlert->okText = '';
|
||||
$storageAlert->message = TplMng::getInstance()->render('mocks/storage/popup', array(
|
||||
'storages' => self::getStoragesData(),
|
||||
'utm_medium' => $utm_medium,
|
||||
), false);
|
||||
$storageAlert->initAlert();
|
||||
|
||||
return $storageAlert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the storage data for the view
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
private static function getStoragesData()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'title' => __('Amazon S3', 'duplicator'),
|
||||
'label' => __('Amazon S3', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/aws.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('Google Drive', 'duplicator'),
|
||||
'label' => __('Google Drive', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/google-drive.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('OneDrive', 'duplicator'),
|
||||
'label' => __('OneDrive', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/onedrive.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('DropBox', 'duplicator'),
|
||||
'label' => __('DropBox', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/dropbox.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('FTP/SFTP', 'duplicator'),
|
||||
'label' => __('FTP/SFTP', 'duplicator'),
|
||||
'fa-class' => 'fas fa-network-wired',
|
||||
),
|
||||
array(
|
||||
'title' => __('Google Cloud Storage', 'duplicator'),
|
||||
'label' => __('Google Cloud Storage', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/google-cloud.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('Back Blaze', 'duplicator'),
|
||||
'label' => __('Back Blaze', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/backblaze.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('Cloudflare R2', 'duplicator'),
|
||||
'label' => __('Cloudflare R2', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/cloudflare.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('DigitalOcean Spaces', 'duplicator'),
|
||||
'label' => __('DigitalOcean Spaces', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/digital-ocean.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('Vultr Object Storage', 'duplicator'),
|
||||
'label' => __('Vultr Object Storage', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/vultr.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('Dream Objects', 'duplicator'),
|
||||
'label' => __('Dream Objects', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/dreamhost.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('Wasabi', 'duplicator'),
|
||||
'label' => __('Wasabi', 'duplicator'),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/wasabi.svg',
|
||||
),
|
||||
array(
|
||||
'title' => __('S3-Compatible Provider', 'duplicator'),
|
||||
'label' => __(
|
||||
'S3-Compatible (Generic) Cloudian, Cloudn, Connectria, Constant, Exoscal, Eucalyptus, Nifty, Nimbula, Minio, etc...',
|
||||
'duplicator'
|
||||
),
|
||||
'iconUrl' => DUPLICATOR_PLUGIN_URL . 'assets/img/aws.svg',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2023, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Controllers;
|
||||
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
|
||||
/**
|
||||
* Welcome screen controller
|
||||
*/
|
||||
class WelcomeController
|
||||
{
|
||||
/**
|
||||
* Hidden welcome page slug.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SLUG = 'duplicator-getting-started';
|
||||
|
||||
/**
|
||||
* Option determining redirect
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const REDIRECT_OPT_KEY = 'duplicator_redirect_to_welcome';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
// If user is in admin ajax or doing cron, return.
|
||||
if (function_exists('wp_doing_ajax') && wp_doing_ajax()) {
|
||||
return;
|
||||
}
|
||||
if (function_exists('wp_doing_cron') && wp_doing_cron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action('admin_menu', array(__CLASS__, 'register'));
|
||||
add_action('admin_head', array(__CLASS__, 'hideMenu'));
|
||||
add_action('admin_init', array(__CLASS__, 'redirect'), 9999);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the pages to be used for the Welcome screen (and tabs).
|
||||
*
|
||||
* These pages will be removed from the Dashboard menu, so they will
|
||||
* not actually show. Sneaky, sneaky.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
// Getting started - shows after installation.
|
||||
$hook_suffix = add_dashboard_page(
|
||||
esc_html__('Welcome to Duplicator', 'duplicator'),
|
||||
esc_html__('Welcome to Duplicator', 'duplicator'),
|
||||
'export',
|
||||
self::SLUG,
|
||||
array(__CLASS__, 'render')
|
||||
);
|
||||
add_action('admin_print_styles-' . $hook_suffix, array(__CLASS__, 'enqueues'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed the dashboard pages from the admin menu.
|
||||
*
|
||||
* This means the pages are still available to us, but hidden.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function hideMenu()
|
||||
{
|
||||
remove_submenu_page('index.php', self::SLUG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Welcome screen redirect.
|
||||
*
|
||||
* This function checks if a new install or update has just occurred. If so,
|
||||
* then we redirect the user to the appropriate page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function redirect()
|
||||
{
|
||||
if (!get_option(self::REDIRECT_OPT_KEY, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to disable the onboarding redirect.
|
||||
*
|
||||
* @since 1.5.11.1
|
||||
*
|
||||
* @param bool $disable True to disable the onboarding redirect.
|
||||
*/
|
||||
if (apply_filters('duplicator_disable_onboarding_redirect', false)) {
|
||||
delete_option(self::REDIRECT_OPT_KEY);
|
||||
return;
|
||||
}
|
||||
|
||||
delete_option(self::REDIRECT_OPT_KEY);
|
||||
|
||||
wp_safe_redirect(admin_url('index.php?page=' . WelcomeController::SLUG));
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueues()
|
||||
{
|
||||
wp_enqueue_style(
|
||||
'dup-welcome',
|
||||
DUPLICATOR_PLUGIN_URL . "assets/css/welcome.css",
|
||||
array(),
|
||||
DUPLICATOR_VERSION
|
||||
);
|
||||
wp_enqueue_script(
|
||||
'duplicator-onboarding',
|
||||
DUPLICATOR_PLUGIN_URL . "assets/js/onboarding.js",
|
||||
array('jquery'),
|
||||
DUPLICATOR_VERSION,
|
||||
true
|
||||
);
|
||||
wp_localize_script(
|
||||
'duplicator-onboarding',
|
||||
'duplicator_onboarding',
|
||||
array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce("duplicator_enable_usage_stats"),
|
||||
'email' => wp_get_current_user()->user_email,
|
||||
'redirect_url' => ControllersManager::getMenuLink(ControllersManager::PACKAGES_SUBMENU_SLUG)
|
||||
)
|
||||
);
|
||||
wp_enqueue_style('dup-font-awesome');
|
||||
wp_enqueue_script('dup-jquery-qtip');
|
||||
wp_enqueue_style('dup-plugin-style');
|
||||
wp_enqueue_style('dup-jquery-qtip');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render welcome screen
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function render()
|
||||
{
|
||||
TplMng::getInstance()->render('admin_pages/welcome/welcome', array(), true);
|
||||
}
|
||||
}
|
||||
619
html/wp-content/plugins/duplicator/src/Core/Bootstrap.php
Normal file
619
html/wp-content/plugins/duplicator/src/Core/Bootstrap.php
Normal file
@@ -0,0 +1,619 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Interface that collects the functions of initial duplicator Bootstrap
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Core;
|
||||
|
||||
use DUP_Constants;
|
||||
use DUP_CTRL_Package;
|
||||
use DUP_CTRL_Tools;
|
||||
use DUP_CTRL_UI;
|
||||
use DUP_Custom_Host_Manager;
|
||||
use DUP_DB;
|
||||
use DUP_LITE_Plugin_Upgrade;
|
||||
use DUP_Log;
|
||||
use DUP_Package;
|
||||
use DUP_Settings;
|
||||
use DUP_UI_Screen;
|
||||
use Duplicator\Controllers\HelpPageController;
|
||||
use Duplicator\Utils\Email\EmailSummaryBootstrap;
|
||||
use Duplicator\Views\AdminNotices;
|
||||
use DUP_Util;
|
||||
use DUP_Web_Services;
|
||||
use Duplicator\Ajax\ServicesDashboard;
|
||||
use Duplicator\Ajax\ServicesEducation;
|
||||
use Duplicator\Ajax\ServicesExtraPlugins;
|
||||
use Duplicator\Ajax\ServicesTools;
|
||||
use Duplicator\Controllers\AboutUsController;
|
||||
use Duplicator\Controllers\EmailSummaryPreviewPageController;
|
||||
use Duplicator\Controllers\WelcomeController;
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Core\Notifications\Notice;
|
||||
use Duplicator\Core\Notifications\NoticeBar;
|
||||
use Duplicator\Core\Notifications\Notifications;
|
||||
use Duplicator\Core\Notifications\Review;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
use Duplicator\Utils\CronUtils;
|
||||
use Duplicator\Utils\ExtraPlugins\CrossPromotion;
|
||||
use Duplicator\Utils\LinkManager;
|
||||
use Duplicator\Views\DashboardWidget;
|
||||
use Duplicator\Views\EducationElements;
|
||||
use Duplicator\Utils\UsageStatistics\StatsBootstrap;
|
||||
|
||||
class Bootstrap
|
||||
{
|
||||
/**
|
||||
* Init plugin
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
add_action('init', array(__CLASS__, 'hookWpInit'));
|
||||
|
||||
if (is_admin()) {
|
||||
add_action('plugins_loaded', array(__CLASS__, 'update'));
|
||||
add_action('plugins_loaded', array(__CLASS__, 'wpfrontIntegrate'));
|
||||
add_action('init', array(__CLASS__, 'loadTextdomain'));
|
||||
|
||||
/* ========================================================
|
||||
* ACTIVATE/DEACTIVE/UPDATE HOOKS
|
||||
* ===================================================== */
|
||||
register_activation_hook(DUPLICATOR_LITE_FILE, array('DUP_LITE_Plugin_Upgrade', 'onActivationAction'));
|
||||
Unistall::registerHooks();
|
||||
}
|
||||
|
||||
CronUtils::init();
|
||||
StatsBootstrap::init();
|
||||
EmailSummaryBootstrap::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called on wordpress hook init action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function hookWpInit()
|
||||
{
|
||||
if (is_admin()) {
|
||||
$GLOBALS['CTRLS_DUP_CTRL_UI'] = new DUP_CTRL_UI();
|
||||
$GLOBALS['CTRLS_DUP_CTRL_Tools'] = new DUP_CTRL_Tools();
|
||||
$GLOBALS['CTRLS_DUP_CTRL_Package'] = new DUP_CTRL_Package();
|
||||
|
||||
if (is_multisite()) {
|
||||
add_action('network_admin_menu', array(__CLASS__, 'menuInit'));
|
||||
add_filter('network_admin_plugin_action_links', array(__CLASS__, 'manageLink'), 10, 2);
|
||||
add_filter('network_admin_plugin_row_meta', array(__CLASS__, 'metaLinks'), 10, 2);
|
||||
} else {
|
||||
add_action('admin_menu', array(__CLASS__, 'menuInit'));
|
||||
add_filter('plugin_action_links', array(__CLASS__, 'manageLink'), 10, 2);
|
||||
add_filter('plugin_row_meta', array(__CLASS__, 'metaLinks'), 10, 2);
|
||||
}
|
||||
|
||||
add_action('admin_init', array(__CLASS__, 'adminInit'));
|
||||
add_action('in_admin_footer', array(__CLASS__, 'pluginFooter' ));
|
||||
add_action('admin_footer', array(__CLASS__, 'adjustProMenuItemClass'));
|
||||
add_action('admin_enqueue_scripts', array(__CLASS__, 'adminEqueueScripts'));
|
||||
|
||||
add_action('wp_ajax_duplicator_active_package_info', 'duplicator_active_package_info');
|
||||
add_action('wp_ajax_duplicator_package_scan', 'duplicator_package_scan');
|
||||
add_action('wp_ajax_duplicator_package_build', 'duplicator_package_build');
|
||||
add_action('wp_ajax_duplicator_package_delete', 'duplicator_package_delete');
|
||||
add_action('wp_ajax_duplicator_duparchive_package_build', 'duplicator_duparchive_package_build');
|
||||
|
||||
add_filter('admin_body_class', array(__CLASS__, 'addBodyClass'));
|
||||
|
||||
//Init Class
|
||||
DUP_Custom_Host_Manager::getInstance()->init();
|
||||
DUP_Settings::init();
|
||||
DUP_Log::Init();
|
||||
DUP_Util::init();
|
||||
DUP_DB::init();
|
||||
MigrationMng::init();
|
||||
Notice::init();
|
||||
NoticeBar::init();
|
||||
Review::init();
|
||||
AdminNotices::init();
|
||||
DUP_Web_Services::init();
|
||||
WelcomeController::init();
|
||||
DashboardWidget::init();
|
||||
EducationElements::init();
|
||||
Notifications::init();
|
||||
EmailSummaryPreviewPageController::init();
|
||||
HelpPageController::init();
|
||||
CrossPromotion::init();
|
||||
$dashboardService = new ServicesDashboard();
|
||||
$dashboardService->init();
|
||||
$extraPlugin = new ServicesExtraPlugins();
|
||||
$extraPlugin->init();
|
||||
$educationService = new ServicesEducation();
|
||||
$educationService->init();
|
||||
$toolsServices = new ServicesTools();
|
||||
$toolsServices->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return admin body classes
|
||||
*
|
||||
* @param string $classes classes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function addBodyClass($classes)
|
||||
{
|
||||
if (ControllersManager::isDuplicatorPage()) {
|
||||
$classes .= ' duplicator-pages';
|
||||
}
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooked into `plugins_loaded`. Routines used to update the plugin
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public static function update()
|
||||
{
|
||||
if (DUPLICATOR_VERSION != get_option(DUP_LITE_Plugin_Upgrade::DUP_VERSION_OPT_KEY)) {
|
||||
DUP_LITE_Plugin_Upgrade::onActivationAction();
|
||||
// $snapShotDirPerm = substr(sprintf("%o", fileperms(DUP_Settings::getSsdirPath())),-4);
|
||||
}
|
||||
load_plugin_textdomain('duplicator');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load text domain for translation
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function loadTextdomain()
|
||||
{
|
||||
load_plugin_textdomain('duplicator', false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* User role editor integration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function wpfrontIntegrate()
|
||||
{
|
||||
if (DUP_Settings::Get('wpfront_integrate')) {
|
||||
do_action('wpfront_user_role_editor_duplicator_init', array('export', 'manage_options', 'read'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooked into `admin_init`. Init routines for all admin pages
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function adminInit()
|
||||
{
|
||||
add_action('in_admin_header', array('Duplicator\\Views\\ViewHelper', 'adminLogoHeader'), 100);
|
||||
|
||||
/* CSS */
|
||||
wp_register_style('dup-jquery-ui', DUPLICATOR_PLUGIN_URL . 'assets/css/jquery-ui.css', null, "1.14.1");
|
||||
wp_register_style('dup-font-awesome', DUPLICATOR_PLUGIN_URL . 'assets/css/font-awesome/css/all.min.css', [], '6.4.2');
|
||||
wp_register_style('dup-plugin-global-style', DUPLICATOR_PLUGIN_URL . 'assets/css/global_admin_style.css', null, DUPLICATOR_VERSION);
|
||||
wp_register_style('dup-plugin-style', DUPLICATOR_PLUGIN_URL . 'assets/css/style.css', array('dup-plugin-global-style'), DUPLICATOR_VERSION);
|
||||
|
||||
wp_register_style('dup-jquery-qtip', DUPLICATOR_PLUGIN_URL . 'assets/js/jquery.qtip/jquery.qtip.min.css', null, '2.2.1');
|
||||
wp_register_style('dup-parsley-style', DUPLICATOR_PLUGIN_URL . 'assets/css/parsley.css', null, '2.3.5');
|
||||
/* JS */
|
||||
wp_register_script('dup-handlebars', DUPLICATOR_PLUGIN_URL . 'assets/js/handlebars.min.js', array('jquery'), '4.0.10');
|
||||
wp_register_script('dup-parsley', DUPLICATOR_PLUGIN_URL . 'assets/js/parsley.min.js', array('jquery'), '1.1.18');
|
||||
wp_register_script('dup-jquery-qtip', DUPLICATOR_PLUGIN_URL . 'assets/js/jquery.qtip/jquery.qtip.min.js', array('jquery'), '2.2.1');
|
||||
|
||||
add_action('admin_head', [DUP_UI_Screen::class, 'getCustomCss']);
|
||||
// Clean tmp folder
|
||||
DUP_Package::not_active_files_tmp_cleanup();
|
||||
|
||||
$unhook_third_party_js = DUP_Settings::Get('unhook_third_party_js');
|
||||
$unhook_third_party_css = DUP_Settings::Get('unhook_third_party_css');
|
||||
if ($unhook_third_party_js || $unhook_third_party_css) {
|
||||
add_action('admin_enqueue_scripts', array(__CLASS__, 'unhookThirdPartyAssets'), 99999, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooked into `admin_menu`. Loads all of the wp left nav admin menus for Duplicator
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function menuInit()
|
||||
{
|
||||
$menuLabel = apply_filters('duplicator_menu_label_duplicator', 'Duplicator');
|
||||
//SVG Icon: See https://websemantics.uk/tools/image-to-data-uri-converter/
|
||||
$hook_prefix = add_menu_page('Duplicator Plugin', $menuLabel, 'export', 'duplicator', null, DUP_Constants::ICON_SVG);
|
||||
add_action('admin_print_scripts-' . $hook_prefix, array(__CLASS__, 'scripts'));
|
||||
add_action('admin_print_styles-' . $hook_prefix, array(__CLASS__, 'styles'));
|
||||
|
||||
//Submenus are displayed in the same order they have in the array
|
||||
$subMenuItems = self::getSubmenuItems();
|
||||
foreach ($subMenuItems as $k => $subMenuItem) {
|
||||
$pageTitle = apply_filters('duplicator_page_title_' . $subMenuItem['menu_slug'], $subMenuItem['page_title']);
|
||||
$menuLabel = apply_filters('duplicator_menu_label_' . $subMenuItem['menu_slug'], $subMenuItem['menu_title']);
|
||||
|
||||
$subMenuItems[$k]['hook_prefix'] = add_submenu_page(
|
||||
$subMenuItem['parent_slug'],
|
||||
$pageTitle,
|
||||
$menuLabel,
|
||||
$subMenuItem['capability'],
|
||||
$subMenuItem['menu_slug'],
|
||||
$subMenuItem['callback'],
|
||||
$k
|
||||
);
|
||||
add_action('admin_print_scripts-' . $subMenuItems[$k]['hook_prefix'], array(__CLASS__, 'scripts'));
|
||||
|
||||
if (isset($subMenuItem['enqueue_style_callback'])) {
|
||||
add_action('admin_print_styles-' . $subMenuItems[$k]['hook_prefix'], $subMenuItem['enqueue_style_callback']);
|
||||
}
|
||||
add_action('admin_print_styles-' . $subMenuItems[$k]['hook_prefix'], array(__CLASS__, 'styles'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submenu datas
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
protected static function getSubmenuItems()
|
||||
{
|
||||
$proTitle = '<span id="dup-link-upgrade-highlight">' . __('Upgrade to Pro', 'duplicator') . '</span>';
|
||||
$dupMenuNew = '<span class="dup-menu-new"> ' . __('NEW!', 'duplicator') . '</span>';
|
||||
return array(
|
||||
array(
|
||||
'parent_slug' => 'duplicator',
|
||||
'page_title' => __('Backups', 'duplicator'),
|
||||
'menu_title' => __('Backups', 'duplicator'),
|
||||
'capability' => 'export',
|
||||
'menu_slug' => ControllersManager::MAIN_MENU_SLUG,
|
||||
'callback' => function () {
|
||||
include(DUPLICATOR_PLUGIN_PATH . 'views/packages/controller.php');
|
||||
}
|
||||
),
|
||||
array(
|
||||
'parent_slug' => 'duplicator',
|
||||
'page_title' => __('Import Backups', 'duplicator'),
|
||||
'menu_title' => __('Import Backups', 'duplicator'),
|
||||
'capability' => 'export',
|
||||
'menu_slug' => ControllersManager::IMPORT_SUBMENU_SLUG,
|
||||
'callback' => function () {
|
||||
TplMng::getInstance()->render('mocks/import/import');
|
||||
},
|
||||
'enqueue_style_callback' => array(__CLASS__, 'mocksStyles')
|
||||
),
|
||||
array(
|
||||
'parent_slug' => 'duplicator',
|
||||
'page_title' => __('Schedule Backups', 'duplicator'),
|
||||
'menu_title' => __('Schedule Backups', 'duplicator') . $dupMenuNew,
|
||||
'capability' => 'export',
|
||||
'menu_slug' => ControllersManager::SCHEDULES_SUBMENU_SLUG,
|
||||
'callback' => function () {
|
||||
TplMng::getInstance()->render('mocks/schedule/schedules');
|
||||
},
|
||||
'enqueue_style_callback' => array(__CLASS__, 'mocksStyles')
|
||||
),
|
||||
array(
|
||||
'parent_slug' => 'duplicator',
|
||||
'page_title' => __('Storage', 'duplicator'),
|
||||
'menu_title' => '<span class="dup-storages-menu-highlight">' . __('Storage', 'duplicator') . '</span>',
|
||||
'capability' => 'export',
|
||||
'menu_slug' => ControllersManager::STORAGE_SUBMENU_SLUG,
|
||||
'callback' => array('Duplicator\\Controllers\\StorageController', 'render'),
|
||||
'enqueue_style_callback' => array(__CLASS__, 'mocksStyles')
|
||||
),
|
||||
array(
|
||||
'parent_slug' => 'duplicator',
|
||||
'page_title' => __('Tools', 'duplicator'),
|
||||
'menu_title' => __('Tools', 'duplicator'),
|
||||
'capability' => 'manage_options',
|
||||
'menu_slug' => ControllersManager::TOOLS_SUBMENU_SLUG,
|
||||
'callback' => function () {
|
||||
include(DUPLICATOR_PLUGIN_PATH . 'views/tools/controller.php');
|
||||
},
|
||||
'enqueue_style_callback' => function () {
|
||||
AboutUsController::enqueues();
|
||||
self::mocksStyles();
|
||||
}
|
||||
),
|
||||
array(
|
||||
'parent_slug' => 'duplicator',
|
||||
'page_title' => __('Settings', 'duplicator'),
|
||||
'menu_title' => __('Settings', 'duplicator'),
|
||||
'capability' => 'manage_options',
|
||||
'menu_slug' => ControllersManager::SETTINGS_SUBMENU_SLUG,
|
||||
'callback' => function () {
|
||||
include(DUPLICATOR_PLUGIN_PATH . 'views/settings/controller.php');
|
||||
}
|
||||
),
|
||||
array(
|
||||
'parent_slug' => 'duplicator',
|
||||
'page_title' => __('About Duplicator', 'duplicator'),
|
||||
'menu_title' => __('About Us', 'duplicator'),
|
||||
'capability' => 'manage_options',
|
||||
'menu_slug' => ControllersManager::ABOUT_US_SUBMENU_SLUG,
|
||||
'callback' => array('Duplicator\\Controllers\\AboutUsController', 'render'),
|
||||
'enqueue_style_callback' => array('Duplicator\\Controllers\\AboutUsController', 'enqueues')
|
||||
),
|
||||
array(
|
||||
'parent_slug' => 'duplicator',
|
||||
'page_title' => $proTitle,
|
||||
'menu_title' => $proTitle,
|
||||
'capability' => 'manage_options',
|
||||
'menu_slug' => LinkManager::getCampaignUrl('admin-menu', 'Upgrade to Pro'),
|
||||
'callback' => null,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooked into `admin_enqueue_scripts`. Init routines for all admin pages
|
||||
*
|
||||
* @access global
|
||||
* @return null
|
||||
*/
|
||||
public static function adminEqueueScripts()
|
||||
{
|
||||
wp_enqueue_script('dup-global-script', DUPLICATOR_PLUGIN_URL . 'assets/js/global-admin-script.js', array('jquery'), DUPLICATOR_VERSION, true);
|
||||
wp_localize_script(
|
||||
'dup-global-script',
|
||||
'dup_global_script_data',
|
||||
array(
|
||||
'nonce_admin_notice_to_dismiss' => wp_create_nonce('duplicator_admin_notice_to_dismiss'),
|
||||
'nonce_settings_callout_to_dismiss' => wp_create_nonce('duplicator_settings_callout_cta_dismiss'),
|
||||
'nonce_packages_bottom_bar_dismiss' => wp_create_nonce('duplicator_packages_bottom_bar_dismiss'),
|
||||
'nonce_email_subscribe' => wp_create_nonce('duplicator_email_subscribe'),
|
||||
'nonce_dashboard_widged_info' => wp_create_nonce("duplicator_dashboad_widget_info"),
|
||||
'nonce_dashboard_widged_dismiss_recommended' => wp_create_nonce("duplicator_dashboad_widget_dismiss_recommended"),
|
||||
'ajaxurl' => admin_url('admin-ajax.php')
|
||||
)
|
||||
);
|
||||
wp_localize_script(
|
||||
'dup-global-script',
|
||||
'l10nDupGlobalScript',
|
||||
array(
|
||||
'subscribe' => esc_html__('Subscribe', 'duplicator'),
|
||||
'subscribed' => esc_html__('Subscribed ✓', 'duplicator'),
|
||||
'subscribing' => esc_html__('Subscribing...', 'duplicator'),
|
||||
'fail' => esc_html__('Failed ✗', 'duplicator'),
|
||||
'emailFail' => esc_html__('Email subscription failed with message: ', 'duplicator'),
|
||||
)
|
||||
);
|
||||
|
||||
wp_enqueue_script('dup-one-click-upgrade-script', DUPLICATOR_PLUGIN_URL . 'assets/js/one-click-upgrade.js', array('jquery'), DUPLICATOR_VERSION, true);
|
||||
wp_localize_script(
|
||||
'dup-one-click-upgrade-script',
|
||||
'dup_one_click_upgrade_script_data',
|
||||
array(
|
||||
'nonce_generate_connect_oth' => wp_create_nonce('duplicator_generate_connect_oth'),
|
||||
'ajaxurl' => admin_url('admin-ajax.php'),
|
||||
'fail_notice_title' => __('Failed to connect to Duplicator Pro.', 'duplicator'),
|
||||
'fail_notice_message_label' => __('Message: ', 'duplicator'),
|
||||
'fail_notice_suggestion' => __('Please try again or contact support if the issue persists.', 'duplicator'),
|
||||
)
|
||||
);
|
||||
|
||||
wp_enqueue_script('dup-dynamic-help', DUPLICATOR_PLUGIN_URL . 'assets/js/dynamic-help.js', array('jquery'), DUPLICATOR_VERSION, true);
|
||||
wp_localize_script(
|
||||
'dup-dynamic-help',
|
||||
'l10nDupDynamicHelp',
|
||||
array(
|
||||
'failMsg' => esc_html__('Failed to load help content!', 'duplicator')
|
||||
)
|
||||
);
|
||||
|
||||
wp_enqueue_script('dup-duplicator-tooltip', DUPLICATOR_PLUGIN_URL . 'assets/js/duplicator-tooltip.js', array('jquery'), DUPLICATOR_VERSION, true);
|
||||
wp_localize_script(
|
||||
'dup-duplicator-tooltip',
|
||||
'l10nDupTooltip',
|
||||
array(
|
||||
'copy' => esc_html__('Copy to clipboard', 'duplicator'),
|
||||
'copied' => esc_html__('copied to clipboard', 'duplicator'),
|
||||
'copyUnable' => esc_html__('Unable to copy', 'duplicator')
|
||||
)
|
||||
);
|
||||
|
||||
wp_enqueue_style('dup-plugin-global-style');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the PRO badge to left sidebar menu item.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function adjustProMenuItemClass()
|
||||
{
|
||||
//Add to footer so it's applied on hovered item too
|
||||
?>
|
||||
<script>jQuery(function($) {
|
||||
$('#dup-link-upgrade-highlight').parent().attr('target','_blank');
|
||||
$('#dup-link-upgrade-highlight').closest('li').addClass('dup-submenu-upgrade-highlight')
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.dup-submenu-upgrade-highlight,
|
||||
.dup-submenu-upgrade-highlight a,
|
||||
.dup-submenu-upgrade-highlight a span#dup-link-upgrade-highlight {
|
||||
background-color: #1da867!important;
|
||||
color: #fff!important;
|
||||
border-color: #fff!important;
|
||||
font-weight: 600!important;
|
||||
}
|
||||
|
||||
.dup-storages-menu-highlight {
|
||||
color: #27d584;
|
||||
}
|
||||
|
||||
#adminmenu .dup-menu-new {
|
||||
color: #f18200;
|
||||
vertical-align: super;
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
padding-left: 2px;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the plugin footer
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function pluginFooter()
|
||||
{
|
||||
if (!ControllersManager::isDuplicatorPage()) {
|
||||
return;
|
||||
}
|
||||
TplMng::getInstance()->render('parts/plugin-footer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all required javascript libs/source for DupPro
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function scripts()
|
||||
{
|
||||
wp_enqueue_script('jquery');
|
||||
wp_enqueue_script('jquery-ui-core');
|
||||
wp_enqueue_script('jquery-ui-progressbar');
|
||||
wp_enqueue_script('dup-parsley');
|
||||
wp_enqueue_script('dup-jquery-qtip');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all CSS style libs/source for DupPro
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function styles()
|
||||
{
|
||||
wp_enqueue_style('dup-jquery-ui');
|
||||
wp_enqueue_style('dup-font-awesome');
|
||||
wp_enqueue_style('dup-plugin-style');
|
||||
wp_enqueue_style('dup-jquery-qtip');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue mock related styles
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function mocksStyles()
|
||||
{
|
||||
wp_enqueue_style(
|
||||
'dup-mocks-styles',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/css/mocks.css',
|
||||
array(),
|
||||
DUPLICATOR_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the manage link in the plugins list
|
||||
*
|
||||
* @param string[] $links links
|
||||
* @param string $file file
|
||||
*
|
||||
* @return string The manage link in the plugins list
|
||||
*/
|
||||
public static function manageLink($links, $file)
|
||||
{
|
||||
static $this_plugin;
|
||||
if (!$this_plugin) {
|
||||
$this_plugin = plugin_basename(DUPLICATOR_LITE_FILE);
|
||||
}
|
||||
|
||||
if ($file == $this_plugin) {
|
||||
/*
|
||||
$settings_link = '<a href="admin.php?page=duplicator">' . esc_html__("Manage", 'duplicator') . '</a>';
|
||||
array_unshift($links, $settings_link);
|
||||
*/
|
||||
$upgrade_link = '<a style="color: #1da867;" class="dup-plugins-list-pro-upgrade" href="' .
|
||||
esc_url(LinkManager::getCampaignUrl('plugin-actions-link')) . '" target="_blank">' .
|
||||
'<strong style="display: inline;">' .
|
||||
esc_html__("Upgrade to Pro", 'duplicator') .
|
||||
'</strong></a>';
|
||||
array_unshift($links, $upgrade_link);
|
||||
}
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds links to the plugins manager page
|
||||
*
|
||||
* @param string[] $links links
|
||||
* @param string $file file
|
||||
*
|
||||
* @return string The meta help link data for the plugins manager
|
||||
*/
|
||||
public static function metaLinks($links, $file)
|
||||
{
|
||||
$plugin = plugin_basename(DUPLICATOR_LITE_FILE);
|
||||
// create link
|
||||
if ($file == $plugin) {
|
||||
$links[] = '<a href="admin.php?page=duplicator" title="' . esc_attr__('Manage Backups', 'duplicator') . '" style="">' .
|
||||
esc_html__('Manage', 'duplicator') .
|
||||
'</a>';
|
||||
return $links;
|
||||
}
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all external styles and scripts coming from other plugins
|
||||
* which may cause compatibility issue, especially with React
|
||||
*
|
||||
* @param string $hook hook
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function unhookThirdPartyAssets($hook)
|
||||
{
|
||||
/*
|
||||
$hook values in duplicator admin pages:
|
||||
toplevel_page_duplicator
|
||||
duplicator_page_duplicator-tools
|
||||
duplicator_page_duplicator-settings
|
||||
duplicator_page_duplicator-gopro
|
||||
*/
|
||||
if (strpos($hook, 'duplicator') !== false && strpos($hook, 'duplicator-pro') === false) {
|
||||
$unhook_third_party_js = DUP_Settings::Get('unhook_third_party_js');
|
||||
$unhook_third_party_css = DUP_Settings::Get('unhook_third_party_css');
|
||||
$assets = array();
|
||||
if ($unhook_third_party_css) {
|
||||
$assets['styles'] = wp_styles();
|
||||
}
|
||||
if ($unhook_third_party_js) {
|
||||
$assets['scripts'] = wp_scripts();
|
||||
}
|
||||
foreach ($assets as $type => $asset) {
|
||||
foreach ($asset->registered as $handle => $dep) {
|
||||
$src = $dep->src;
|
||||
// test if the src is coming from /wp-admin/ or /wp-includes/ or /wp-fsqm-pro/.
|
||||
if (
|
||||
is_string($src) && // For some built-ins, $src is true|false
|
||||
strpos($src, 'wp-admin') === false &&
|
||||
strpos($src, 'wp-include') === false &&
|
||||
// things below are specific to your plugin, so change them
|
||||
strpos($src, 'duplicator') === false &&
|
||||
strpos($src, 'woocommerce') === false &&
|
||||
strpos($src, 'jetpack') === false &&
|
||||
strpos($src, 'debug-bar') === false
|
||||
) {
|
||||
'scripts' === $type ? wp_dequeue_script($handle) : wp_dequeue_style($handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Singlethon class that manages the various controllers of the administration of wordpress
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Core\Controllers;
|
||||
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
|
||||
final class ControllersManager
|
||||
{
|
||||
const MAIN_MENU_SLUG = 'duplicator';
|
||||
const PACKAGES_SUBMENU_SLUG = 'duplicator';
|
||||
const IMPORT_SUBMENU_SLUG = 'duplicator-import';
|
||||
const SCHEDULES_SUBMENU_SLUG = 'duplicator-schedules';
|
||||
const STORAGE_SUBMENU_SLUG = 'duplicator-storage';
|
||||
const ABOUT_US_SUBMENU_SLUG = 'duplicator-about-us';
|
||||
const TEMPLATES_SUBMENU_SLUG = 'duplicator-templates';
|
||||
const TOOLS_SUBMENU_SLUG = 'duplicator-tools';
|
||||
const SETTINGS_SUBMENU_SLUG = 'duplicator-settings';
|
||||
const DEBUG_SUBMENU_SLUG = 'duplicator-debug';
|
||||
const UPSELL_SUBMENU_SLUG = 'duplicator-pro';
|
||||
|
||||
const QUERY_STRING_MENU_KEY_L1 = 'page';
|
||||
const QUERY_STRING_MENU_KEY_L2 = 'tab';
|
||||
const QUERY_STRING_MENU_KEY_L3 = 'subtab';
|
||||
const QUERY_STRING_MENU_KEY_ACTION = 'action';
|
||||
|
||||
/**
|
||||
* Return current menu levels
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getMenuLevels()
|
||||
{
|
||||
$result = array();
|
||||
$exChars = '-_';
|
||||
$result[self::QUERY_STRING_MENU_KEY_L1] = SnapUtil::sanitizeStrictInput(
|
||||
SnapUtil::INPUT_REQUEST,
|
||||
self::QUERY_STRING_MENU_KEY_L1,
|
||||
null,
|
||||
$exChars
|
||||
);
|
||||
$result[self::QUERY_STRING_MENU_KEY_L2] = SnapUtil::sanitizeStrictInput(
|
||||
SnapUtil::INPUT_REQUEST,
|
||||
self::QUERY_STRING_MENU_KEY_L2,
|
||||
null,
|
||||
$exChars
|
||||
);
|
||||
$result[self::QUERY_STRING_MENU_KEY_L3] = SnapUtil::sanitizeStrictInput(
|
||||
SnapUtil::INPUT_REQUEST,
|
||||
self::QUERY_STRING_MENU_KEY_L3,
|
||||
null,
|
||||
$exChars
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if current page is a duplicator page
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isDuplicatorPage()
|
||||
{
|
||||
if (!is_admin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (SnapUtil::sanitizeStrictInput(SnapUtil::INPUT_REQUEST, 'page', '', '-_ ')) {
|
||||
case self::MAIN_MENU_SLUG:
|
||||
case self::PACKAGES_SUBMENU_SLUG:
|
||||
case self::IMPORT_SUBMENU_SLUG:
|
||||
case self::SCHEDULES_SUBMENU_SLUG:
|
||||
case self::STORAGE_SUBMENU_SLUG:
|
||||
case self::ABOUT_US_SUBMENU_SLUG:
|
||||
case self::TEMPLATES_SUBMENU_SLUG:
|
||||
case self::TOOLS_SUBMENU_SLUG:
|
||||
case self::SETTINGS_SUBMENU_SLUG:
|
||||
case self::DEBUG_SUBMENU_SLUG:
|
||||
case self::UPSELL_SUBMENU_SLUG:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current action key or false if not exists
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public static function getAction()
|
||||
{
|
||||
return SnapUtil::sanitizeStrictInput(
|
||||
SnapUtil::INPUT_REQUEST,
|
||||
self::QUERY_STRING_MENU_KEY_ACTION,
|
||||
false,
|
||||
'-_'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check current page
|
||||
*
|
||||
* @param string $page page key
|
||||
* @param null|string $tabL1 tab level 1 key, null not check
|
||||
* @param null|string $tabL2 tab level 12key, null not check
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isCurrentPage($page, $tabL1 = null, $tabL2 = null)
|
||||
{
|
||||
$levels = self::getMenuLevels();
|
||||
|
||||
if ($page !== $levels[self::QUERY_STRING_MENU_KEY_L1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_null($tabL1) && $tabL1 !== $levels[self::QUERY_STRING_MENU_KEY_L2]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_null($tabL1) && !is_null($tabL2) && $tabL2 !== $levels[self::QUERY_STRING_MENU_KEY_L3]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current menu page URL
|
||||
*
|
||||
* @param array $extraData extra value in query string key=val
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCurrentLink($extraData = array())
|
||||
{
|
||||
$levels = self::getMenuLevels();
|
||||
return self::getMenuLink(
|
||||
$levels[self::QUERY_STRING_MENU_KEY_L1],
|
||||
$levels[self::QUERY_STRING_MENU_KEY_L2],
|
||||
$levels[self::QUERY_STRING_MENU_KEY_L3],
|
||||
$extraData
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return menu page URL
|
||||
*
|
||||
* @param string $page page slug
|
||||
* @param string $subL2 tab level 1 slug, null not set
|
||||
* @param string $subL3 tab level 2 slug, null not set
|
||||
* @param array $extraData extra value in query string key=val
|
||||
* @param bool $relative if true return relative path or absolute
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMenuLink($page, $subL2 = null, $subL3 = null, $extraData = array(), $relative = true)
|
||||
{
|
||||
$data = $extraData;
|
||||
|
||||
$data[self::QUERY_STRING_MENU_KEY_L1] = $page;
|
||||
|
||||
if (!empty($subL2)) {
|
||||
$data[self::QUERY_STRING_MENU_KEY_L2] = $subL2;
|
||||
}
|
||||
|
||||
if (!empty($subL3)) {
|
||||
$data[self::QUERY_STRING_MENU_KEY_L3] = $subL3;
|
||||
}
|
||||
|
||||
if ($relative) {
|
||||
$url = self_admin_url('admin.php', 'relative');
|
||||
} else {
|
||||
if (is_multisite()) {
|
||||
$url = network_admin_url('admin.php');
|
||||
} else {
|
||||
$url = admin_url('admin.php');
|
||||
}
|
||||
}
|
||||
return $url . '?' . http_build_query($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return create package link
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getPackageBuildUrl()
|
||||
{
|
||||
return self::getMenuLink(
|
||||
self::PACKAGES_SUBMENU_SLUG,
|
||||
'new1',
|
||||
null,
|
||||
array(
|
||||
'inner_page' => 'new1',
|
||||
'_wpnonce' => wp_create_nonce('new1-package')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return package detail link
|
||||
*
|
||||
* @param int $packageId package id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getPackageDetailUrl($packageId)
|
||||
{
|
||||
return self::getMenuLink(
|
||||
self::PACKAGES_SUBMENU_SLUG,
|
||||
'detail',
|
||||
null,
|
||||
array(
|
||||
'action' => 'detail',
|
||||
'id' => $packageId
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
547
html/wp-content/plugins/duplicator/src/Core/MigrationMng.php
Normal file
547
html/wp-content/plugins/duplicator/src/Core/MigrationMng.php
Normal file
@@ -0,0 +1,547 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Utility class managing th emigration data
|
||||
*/
|
||||
|
||||
namespace Duplicator\Core;
|
||||
|
||||
use DUP_Archive;
|
||||
use DUP_CTRL_Tools;
|
||||
use DUP_Settings;
|
||||
use DUP_Util;
|
||||
use Duplicator\Libs\Snap\SnapWP;
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use Duplicator\Utils\CachesPurge\CachesPurge;
|
||||
use Duplicator\Utils\UsageStatistics\CommStats;
|
||||
use Duplicator\Utils\UsageStatistics\PluginData;
|
||||
use Duplicator\Views\AdminNotices;
|
||||
use Error;
|
||||
use Exception;
|
||||
|
||||
class MigrationMng
|
||||
{
|
||||
const HOOK_FIRST_LOGIN_AFTER_INSTALL = 'duplicator_first_login_after_install';
|
||||
const HOOK_BOTTOM_MIGRATION_MESSAGE = 'duplicator_bottom_migration_message';
|
||||
const FIRST_LOGIN_OPTION = 'duplicator_first_login_after_install';
|
||||
const MIGRATION_DATA_OPTION = 'duplicator_migration_data';
|
||||
const CLEAN_INSTALL_REPORT_OPTION = 'duplicator_clean_install_report';
|
||||
|
||||
/**
|
||||
* messages to be displayed in the successful migration box
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $migrationCleanupReport = array(
|
||||
'removed' => array(),
|
||||
'stored' => array(),
|
||||
'instFile' => array()
|
||||
);
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
add_action('admin_init', array(__CLASS__, 'adminInit'));
|
||||
add_action(self::HOOK_FIRST_LOGIN_AFTER_INSTALL, function ($migrationData) {
|
||||
DUP_Util::initSnapshotDirectory();
|
||||
});
|
||||
add_action(self::HOOK_FIRST_LOGIN_AFTER_INSTALL, array(__CLASS__, 'removeFirstLoginOption'));
|
||||
add_action(self::HOOK_FIRST_LOGIN_AFTER_INSTALL, array(__CLASS__, 'renameInstallersPhpFiles'));
|
||||
add_action(self::HOOK_FIRST_LOGIN_AFTER_INSTALL, array(__CLASS__, 'storeMigrationFiles'));
|
||||
add_action(self::HOOK_FIRST_LOGIN_AFTER_INSTALL, array(__CLASS__, 'setDupSettingsAfterInstall'));
|
||||
add_action(self::HOOK_FIRST_LOGIN_AFTER_INSTALL, array(__CLASS__, 'usageStatistics'));
|
||||
// save cleanup report after actions
|
||||
add_action(self::HOOK_FIRST_LOGIN_AFTER_INSTALL, array(__CLASS__, 'saveCleanupReport'), 100);
|
||||
|
||||
// LAST BEACAUSE MAKE A WP_REDIRECT
|
||||
add_action(self::HOOK_FIRST_LOGIN_AFTER_INSTALL, array(__CLASS__, 'autoCleanFileAfterInstall'), 99999);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Init function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function adminInit()
|
||||
{
|
||||
if (self::isFirstLoginAfterInstall()) {
|
||||
add_action('current_screen', array(__CLASS__, 'wpAdminHook'), 99999);
|
||||
update_option(AdminNotices::OPTION_KEY_MIGRATION_SUCCESS_NOTICE, true);
|
||||
do_action(self::HOOK_FIRST_LOGIN_AFTER_INSTALL, self::getMigrationData());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Hook function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function wpAdminHook()
|
||||
{
|
||||
if (!DUP_CTRL_Tools::isToolPage()) {
|
||||
wp_redirect(DUP_CTRL_Tools::getDiagnosticURL(false));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isFirstLoginAfterInstall()
|
||||
{
|
||||
if (is_user_logged_in() && get_option(self::FIRST_LOGIN_OPTION, false)) {
|
||||
if (is_multisite()) {
|
||||
if (is_super_admin()) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (current_user_can('manage_options')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge all caches
|
||||
*
|
||||
* @return string[] // messages
|
||||
*/
|
||||
public static function purgeCaches()
|
||||
{
|
||||
if (
|
||||
self::getMigrationData('restoreBackupMode') ||
|
||||
in_array(self::getMigrationData('installType'), array(4,5,6,7)) //update with define when installerstat will be in namespace
|
||||
) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return CachesPurge::purgeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean after install
|
||||
*
|
||||
* @param array $migrationData migration data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function usageStatistics($migrationData)
|
||||
{
|
||||
$migrationData = (object) $migrationData;
|
||||
PluginData::getInstance()->updateFromMigrateData($migrationData);
|
||||
CommStats::installerSend();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $migrationData Migration data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function autoCleanFileAfterInstall($migrationData)
|
||||
{
|
||||
if ($migrationData == false || $migrationData['cleanInstallerFiles'] == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_redirect(DUP_CTRL_Tools::getCleanFilesAcrtionUrl(false));
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $migrationData Migration data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setDupSettingsAfterInstall($migrationData)
|
||||
{
|
||||
flush_rewrite_rules(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* return cleanup report
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getCleanupReport()
|
||||
{
|
||||
$option = get_option(self::CLEAN_INSTALL_REPORT_OPTION);
|
||||
if (is_array($option)) {
|
||||
self::$migrationCleanupReport = array_merge(self::$migrationCleanupReport, $option);
|
||||
}
|
||||
|
||||
return self::$migrationCleanupReport;
|
||||
}
|
||||
|
||||
/**
|
||||
* save clean up report in wordpress options
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function saveCleanupReport()
|
||||
{
|
||||
return add_option(self::CLEAN_INSTALL_REPORT_OPTION, self::$migrationCleanupReport, '', 'no');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $migrationData Migration data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function removeFirstLoginOption($migrationData)
|
||||
{
|
||||
delete_option(self::FIRST_LOGIN_OPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @staticvar array $migrationData
|
||||
*
|
||||
* @param string|null $key Key to get from migration data
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getMigrationData($key = null)
|
||||
{
|
||||
static $migrationData = null;
|
||||
if (is_null($migrationData)) {
|
||||
$migrationData = get_option(self::MIGRATION_DATA_OPTION, false);
|
||||
if (is_string($migrationData)) {
|
||||
$migrationData = json_decode($migrationData, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($key)) {
|
||||
return $migrationData;
|
||||
} elseif (isset($migrationData[$key])) {
|
||||
return $migrationData[$key];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSaveModeWarning()
|
||||
{
|
||||
switch (self::getMigrationData('safeMode')) {
|
||||
case 1:
|
||||
//safe_mode basic
|
||||
return __('NOTICE: Safe mode (Basic) was enabled during install, be sure to re-enable all your plugins.', 'duplicator');
|
||||
case 2:
|
||||
//safe_mode advance
|
||||
return __('NOTICE: Safe mode (Advanced) was enabled during install, be sure to re-enable all your plugins.', 'duplicator');
|
||||
case 0:
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the root path and in case there are installer files without hashes rename them.
|
||||
*
|
||||
* @param integer $fileTimeDelay If the file is younger than $fileTimeDelay seconds then it is not renamed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function renameInstallersPhpFiles($fileTimeDelay = 0)
|
||||
{
|
||||
$fileTimeDelay = is_numeric($fileTimeDelay) ? (int) $fileTimeDelay : 0;
|
||||
|
||||
$pathsTocheck = array(
|
||||
SnapIO::safePathTrailingslashit(ABSPATH),
|
||||
SnapIO::safePathTrailingslashit(SnapWP::getHomePath()),
|
||||
SnapIO::safePathTrailingslashit(WP_CONTENT_DIR)
|
||||
);
|
||||
|
||||
$migrationData = self::getMigrationData();
|
||||
if (isset($migrationData['installerPath'])) {
|
||||
$pathsTocheck[] = SnapIO::safePathTrailingslashit(dirname($migrationData['installerPath']));
|
||||
}
|
||||
if (isset($migrationData['dupInstallerPath'])) {
|
||||
$pathsTocheck[] = SnapIO::safePathTrailingslashit(dirname($migrationData['dupInstallerPath']));
|
||||
}
|
||||
$pathsTocheck = array_unique($pathsTocheck);
|
||||
|
||||
$filesToCheck = array();
|
||||
foreach ($pathsTocheck as $cFolder) {
|
||||
if (
|
||||
!is_dir($cFolder) ||
|
||||
!is_writable($cFolder) // rename permissions
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$cFile = $cFolder . 'installer.php';
|
||||
if (
|
||||
!is_file($cFile) ||
|
||||
!SnapIO::chmod($cFile, 'u+rw') ||
|
||||
!is_readable($cFile)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$filesToCheck[] = $cFile;
|
||||
}
|
||||
|
||||
$installerTplCheck = '/const\s+ARCHIVE_FILENAME\s*=\s*[\'"](.+?)[\'"]\s*;.*const\s+PACKAGE_HASH\s*=\s*[\'"](.+?)[\'"]\s*;/s';
|
||||
|
||||
foreach ($filesToCheck as $file) {
|
||||
$fileName = basename($file);
|
||||
|
||||
if ($fileTimeDelay > 0 && (time() - filemtime($file)) < $fileTimeDelay) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($content = @file_get_contents($file, false, null)) === false) {
|
||||
continue;
|
||||
}
|
||||
$matches = null;
|
||||
if (preg_match($installerTplCheck, $content, $matches) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$archiveName = $matches[1];
|
||||
$hash = $matches[2];
|
||||
$matches = null;
|
||||
|
||||
|
||||
if (preg_match(DUPLICATOR_ARCHIVE_REGEX_PATTERN, $archiveName, $matches) !== 1) {
|
||||
if (SnapIO::unlink($file)) {
|
||||
self::$migrationCleanupReport['instFile'][] = "<div class='failure'>"
|
||||
. "<i class='fa fa-check green'></i> "
|
||||
. sprintf(__('Installer file <b>%s</b> removed for security reasons', 'duplicator'), esc_html($fileName))
|
||||
. "</div>";
|
||||
} else {
|
||||
self::$migrationCleanupReport['instFile'][] = "<div class='success'>"
|
||||
. '<i class="fa fa-exclamation-triangle red"></i> '
|
||||
. sprintf(__('Can\'t remove installer file <b>%s</b>, please remove it for security reasons', 'duplicator'), esc_html($fileName))
|
||||
. '</div>';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$archiveHash = $matches[1];
|
||||
if (strpos($file, $archiveHash) === false) {
|
||||
if (SnapIO::rename($file, dirname($file) . '/' . $archiveHash . '_installer.php', true)) {
|
||||
self::$migrationCleanupReport['instFile'][] = "<div class='failure'>"
|
||||
. "<i class='fa fa-check green'></i> "
|
||||
. sprintf(__('Installer file <b>%s</b> renamed with HASH', 'duplicator'), esc_html($fileName))
|
||||
. "</div>";
|
||||
} else {
|
||||
self::$migrationCleanupReport['instFile'][] = "<div class='success'>"
|
||||
. '<i class="fa fa-exclamation-triangle red"></i> '
|
||||
. sprintf(
|
||||
__('Can\'t rename installer file <b>%s</b> with HASH, please remove it for security reasons', 'duplicator'),
|
||||
esc_html($fileName)
|
||||
)
|
||||
. '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $migrationData Migration data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function storeMigrationFiles($migrationData)
|
||||
{
|
||||
$ssdInstallerPath = DUP_Settings::getSsdirInstallerPath();
|
||||
wp_mkdir_p($ssdInstallerPath);
|
||||
SnapIO::emptyDir($ssdInstallerPath);
|
||||
SnapIO::createSilenceIndex($ssdInstallerPath);
|
||||
|
||||
$filesToMove = array(
|
||||
$migrationData['installerLog'],
|
||||
$migrationData['installerBootLog'],
|
||||
$migrationData['origFileFolderPath']
|
||||
);
|
||||
|
||||
foreach ($filesToMove as $path) {
|
||||
if (file_exists($path)) {
|
||||
if (SnapIO::rcopy($path, $ssdInstallerPath . '/' . basename($path), true)) {
|
||||
self::$migrationCleanupReport['stored'] = "<div class='success'>"
|
||||
. "<i class='fa fa-check'></i> "
|
||||
. __('Original files folder moved in installer backup directory', 'duplicator') . " - " . esc_html($path) .
|
||||
"</div>";
|
||||
} else {
|
||||
self::$migrationCleanupReport['stored'] = "<div class='success'>"
|
||||
. '<i class="fa fa-exclamation-triangle"></i> '
|
||||
. sprintf(__('Can\'t move %s to %s', 'duplicator'), esc_html($path), $ssdInstallerPath)
|
||||
. '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getStoredMigrationLists()
|
||||
{
|
||||
if (($migrationData = self::getMigrationData()) == false) {
|
||||
$filesToCheck = array();
|
||||
} else {
|
||||
$filesToCheck = array(
|
||||
$migrationData['installerLog'] => __('Installer log', 'duplicator'),
|
||||
$migrationData['installerBootLog'] => __('Installer boot log', 'duplicator'),
|
||||
$migrationData['origFileFolderPath'] => __('Original files folder', 'duplicator')
|
||||
);
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($filesToCheck as $path => $label) {
|
||||
$storedPath = DUP_Settings::getSsdirInstallerPath() . '/' . basename($path);
|
||||
if (!file_exists($storedPath)) {
|
||||
continue;
|
||||
}
|
||||
$result[$storedPath] = $label;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function haveFileToClean()
|
||||
{
|
||||
return count(self::checkInstallerFilesList()) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all the installer files and directory by name and full path
|
||||
*
|
||||
* @remarks
|
||||
* FILES: installer.php, installer-backup.php, dup-installer-bootlog__[HASH].txt
|
||||
* DIRS: dup-installer
|
||||
* Last set is for lazy developer cleanup files that a developer may have
|
||||
* accidentally left around lets be proactive for the user just in case.
|
||||
*
|
||||
* @return [string] // [file_name]
|
||||
*/
|
||||
public static function getGenericInstallerFiles()
|
||||
{
|
||||
return array(
|
||||
'installer.php',
|
||||
'[HASH]installer-backup.php',
|
||||
'dup-installer',
|
||||
'dup-installer[HASH]',
|
||||
'dup-installer-bootlog__[HASH].txt',
|
||||
'[HASH]_archive.zip|daf'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string[]
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function checkInstallerFilesList()
|
||||
{
|
||||
$migrationData = self::getMigrationData();
|
||||
|
||||
$foldersToChkeck = array(
|
||||
SnapIO::safePathTrailingslashit(ABSPATH),
|
||||
SnapWP::getHomePath(),
|
||||
);
|
||||
|
||||
$result = array();
|
||||
|
||||
if (!empty($migrationData)) {
|
||||
if (
|
||||
file_exists($migrationData['archivePath']) &&
|
||||
!DUP_Archive::isBackupPathChild($migrationData['archivePath'])
|
||||
) {
|
||||
$result[] = $migrationData['archivePath'];
|
||||
}
|
||||
if (
|
||||
self::isInstallerFile($migrationData['installerPath']) &&
|
||||
!DUP_Archive::isBackupPathChild($migrationData['archivePath'])
|
||||
) {
|
||||
$result[] = $migrationData['installerPath'];
|
||||
}
|
||||
if (file_exists($migrationData['installerBootLog'])) {
|
||||
$result[] = $migrationData['installerBootLog'];
|
||||
}
|
||||
if (file_exists($migrationData['dupInstallerPath'])) {
|
||||
$result[] = $migrationData['dupInstallerPath'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($foldersToChkeck as $folder) {
|
||||
$result = array_merge($result, SnapIO::regexGlob($folder, array(
|
||||
'regexFile' => array(
|
||||
DUPLICATOR_ARCHIVE_REGEX_PATTERN,
|
||||
DUPLICATOR_INSTALLER_REGEX_PATTERN,
|
||||
DUPLICATOR_DUP_INSTALLER_BOOTLOG_REGEX_PATTERN,
|
||||
DUPLICATOR_DUP_INSTALLER_OWRPARAM_REGEX_PATTERN
|
||||
),
|
||||
'regexFolder' => array(
|
||||
DUPLICATOR_DUP_INSTALLER_FOLDER_REGEX_PATTERN
|
||||
)
|
||||
)));
|
||||
}
|
||||
|
||||
$result = array_map(array('Duplicator\\Libs\\Snap\\SnapIO', 'safePathUntrailingslashit'), $result);
|
||||
return array_unique($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path string Path to check
|
||||
*
|
||||
* @return bool true if the file at current path is the installer file
|
||||
*/
|
||||
public static function isInstallerFile($path)
|
||||
{
|
||||
if (!is_file($path) || !is_array($last5Lines = SnapIO::getLastLinesOfFile($path, 5)) || empty($last5Lines)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return strpos(implode("", $last5Lines), "DUPLICATOR_INSTALLER_EOF") !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the installer files and directory
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function cleanMigrationFiles()
|
||||
{
|
||||
$cleanList = self::checkInstallerFilesList();
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($cleanList as $path) {
|
||||
try {
|
||||
$success = (SnapIO::rrmdir($path) !== false);
|
||||
} catch (Exception $ex) {
|
||||
$success = false;
|
||||
} catch (Error $ex) {
|
||||
$success = false;
|
||||
}
|
||||
|
||||
$result[$path] = $success;
|
||||
}
|
||||
|
||||
delete_option(self::CLEAN_INSTALL_REPORT_OPTION);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Core\Notifications;
|
||||
|
||||
class Notice
|
||||
{
|
||||
/**
|
||||
* Not dismissible.
|
||||
*
|
||||
* Constant attended to use as the value of the $args['dismiss'] argument.
|
||||
* DISMISS_NONE means that the notice is not dismissible.
|
||||
*/
|
||||
const DISMISS_NONE = 0;
|
||||
|
||||
/**
|
||||
* Dismissible global.
|
||||
*
|
||||
* Constant attended to use as the value of the $args['dismiss'] argument.
|
||||
* DISMISS_GLOBAL means that the notice will have the dismiss button, and after clicking this button, the notice will be dismissed for all users.
|
||||
*/
|
||||
const DISMISS_GLOBAL = 1;
|
||||
|
||||
/**
|
||||
* Dismissible per user.
|
||||
*
|
||||
* Constant attended to use as the value of the $args['dismiss'] argument.
|
||||
* DISMISS_USER means that the notice will have the dismiss button, and after clicking this button, the notice will be dismissed only for the current user..
|
||||
*/
|
||||
const DISMISS_USER = 2;
|
||||
|
||||
/**
|
||||
* Constant for notice type info with gray left border
|
||||
*/
|
||||
const NOTICE_TYPE_INFO = 'info';
|
||||
|
||||
/**
|
||||
* Constant for notice type warning with yellow left border
|
||||
*/
|
||||
const NOTICE_TYPE_WARNING = 'warning';
|
||||
|
||||
/**
|
||||
* Constant for notice type warning with red left border
|
||||
*/
|
||||
const NOTICE_TYPE_ERROR = 'error';
|
||||
|
||||
/**
|
||||
* Constant for notice type warning with green left border
|
||||
*/
|
||||
const NOTICE_TYPE_SUCCESS = 'success';
|
||||
|
||||
/**
|
||||
* Constant for notice id default prefix
|
||||
*/
|
||||
const DEFAULT_PREFIX = 'dup-notice-';
|
||||
|
||||
/**
|
||||
* Constant for addition notice id prefix in case it's a global notice
|
||||
*/
|
||||
const GLOBAL_PREFIX = 'global-';
|
||||
|
||||
/**
|
||||
* The wp-options key in which the notices are stored
|
||||
*/
|
||||
const DISMISSED_NOTICES_OPTKEY = 'duplicator_dismissed_admin_notices';
|
||||
|
||||
/**
|
||||
* Notices.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $notices = array();
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
static::hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function hooks()
|
||||
{
|
||||
add_action('admin_notices', array(__CLASS__, 'display'), PHP_INT_MAX);
|
||||
add_action('wp_ajax_dup_notice_dismiss', array(__CLASS__, 'dismissAjax'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function enqueues()
|
||||
{
|
||||
wp_enqueue_script(
|
||||
'dup-admin-notices',
|
||||
DUPLICATOR_PLUGIN_URL . "assets/js/notifications/notices.js",
|
||||
array('jquery'),
|
||||
DUPLICATOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'dup-admin-notices',
|
||||
'dup_admin_notices',
|
||||
array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('duplicator-admin-notice'),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the notices.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function display()
|
||||
{
|
||||
|
||||
$dismissed_notices = get_user_meta(get_current_user_id(), self::DISMISSED_NOTICES_OPTKEY, true);
|
||||
$dismissed_notices = is_array($dismissed_notices) ? $dismissed_notices : array();
|
||||
$dismissed_notices = array_merge($dismissed_notices, (array)get_option(self::DISMISSED_NOTICES_OPTKEY, array()));
|
||||
|
||||
foreach (self::$notices as $slug => $notice) {
|
||||
if (isset($dismissed_notices[$slug])) {
|
||||
unset(self::$notices[$slug]);
|
||||
}
|
||||
}
|
||||
|
||||
$output = implode('', self::$notices);
|
||||
|
||||
echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
// Enqueue script only when it's needed.
|
||||
if (strpos($output, 'is-dismissible') !== false) {
|
||||
self::enqueues();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notice to the registry.
|
||||
*
|
||||
* @param string $message Message to display.
|
||||
* @param string $slug Unique slug identifying the notice
|
||||
* @param string $type Type of the notice. Can be [ '' (default) | 'info' | 'error' | 'success' | 'warning' ].
|
||||
* @param array $args The array of additional arguments. Please see the $defaults array below.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function add($message, $slug, $type = '', $args = array())
|
||||
{
|
||||
$defaults = array(
|
||||
'dismiss' => self::DISMISS_NONE, // Dismissible level: one of the self::DISMISS_* const. By default notice is not dismissible.
|
||||
'autop' => true, // `false` if not needed to pass message through wpautop().
|
||||
'class' => '' // Additional CSS class.
|
||||
);
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
$dismiss = (int)$args['dismiss'];
|
||||
$classes = array();
|
||||
|
||||
if (!empty($type)) {
|
||||
$classes[] = 'notice-' . esc_attr(sanitize_key($type));
|
||||
}
|
||||
|
||||
if (!empty($args['class'])) {
|
||||
$classes[] = esc_attr(sanitize_key($args['class']));
|
||||
}
|
||||
|
||||
if ($dismiss > self::DISMISS_NONE) {
|
||||
$classes[] = 'is-dismissible';
|
||||
}
|
||||
|
||||
$id = $dismiss === self::DISMISS_GLOBAL ? self::DEFAULT_PREFIX . self::GLOBAL_PREFIX . $slug : self::DEFAULT_PREFIX . $slug;
|
||||
$message = $args['autop'] ? wpautop($message) : $message;
|
||||
$notice = sprintf(
|
||||
'<div class="notice dup-notice %s" id="%s">%s</div>',
|
||||
esc_attr(implode(' ', $classes)),
|
||||
esc_attr($id),
|
||||
$message
|
||||
);
|
||||
|
||||
self::$notices[$slug] = $notice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multistep notice.
|
||||
*
|
||||
* @param array $steps Array of info for each step.
|
||||
* @param string $slug Unique slug identifying the notice
|
||||
* @param string $type Type of the notice. Can be [ '' (default) | 'info' | 'error' | 'success' | 'warning' ].
|
||||
* @param array $args Array of additional arguments. Details in the self::add() method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addMultistep($steps, $slug, $type = '', $args = array())
|
||||
{
|
||||
$content = '<div class="dup-multi-notice">';
|
||||
foreach ($steps as $i => $step) {
|
||||
$hide = $i === 0 ? '' : ' style="display: none;"';
|
||||
$content .= '<div class="dup-multi-notice-step dup-multi-notice-step-' . $i . '"' . $hide . '>';
|
||||
|
||||
$content .= $step['message'];
|
||||
$content .= "<p>";
|
||||
foreach ($step["links"] as $link) {
|
||||
$url = isset($link['url']) ? $link['url'] : "#";
|
||||
$target = isset($link['url']) ? 'target="_blank"' : '';
|
||||
$switch = isset($link['switch']) ? ' data-step="' . $link['switch'] . '"' : '';
|
||||
$dismiss = isset($link['dismiss']) && $link['dismiss'] ? ' class="dup-notice-dismiss"' : '';
|
||||
|
||||
$content .= '<a href="' . $url . '"' . $dismiss . $switch . $target . '>' . $link['text'] . '</a><br>';
|
||||
}
|
||||
$content .= "</p>";
|
||||
$content .= "</div>";
|
||||
}
|
||||
$content .= "</div>";
|
||||
|
||||
self::add($content, $slug, $type, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add info notice.
|
||||
*
|
||||
* @param string $message Message to display.
|
||||
* @param string $slug Unique slug identifying the notice
|
||||
* @param array $args Array of additional arguments. Details in the self::add() method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function info($message, $slug, $args = array())
|
||||
{
|
||||
|
||||
self::add($message, $slug, self::NOTICE_TYPE_INFO, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add error notice.
|
||||
*
|
||||
* @param string $message Message to display.
|
||||
* @param string $slug Unique slug identifying the notice
|
||||
* @param array $args Array of additional arguments. Details in the self::add() method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function error($message, $slug, $args = array())
|
||||
{
|
||||
|
||||
self::add($message, $slug, self::NOTICE_TYPE_ERROR, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add success notice.
|
||||
*
|
||||
* @param string $message Message to display.
|
||||
* @param string $slug Unique slug identifying the notice
|
||||
* @param array $args Array of additional arguments. Details in the self::add() method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function success($message, $slug, $args = array())
|
||||
{
|
||||
|
||||
self::add($message, $slug, self::NOTICE_TYPE_SUCCESS, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add warning notice.
|
||||
*
|
||||
* @param string $message Message to display.
|
||||
* @param string $slug Unique slug identifying the notice
|
||||
* @param array $args Array of additional arguments. Details in the self::add() method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function warning($message, $slug, $args = array())
|
||||
{
|
||||
|
||||
self::add($message, $slug, self::NOTICE_TYPE_WARNING, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX routine that updates dismissed notices meta data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function dismissAjax()
|
||||
{
|
||||
|
||||
// Run a security check.
|
||||
check_ajax_referer('duplicator-admin-notice', 'nonce');
|
||||
|
||||
// Sanitize POST data.
|
||||
$post = array_map('sanitize_key', wp_unslash($_POST));
|
||||
|
||||
// Update notices meta data.
|
||||
if (strpos($post['id'], self::GLOBAL_PREFIX) !== false) {
|
||||
// Check for permissions.
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
$notices = self::dismissGlobal($post['id']);
|
||||
$level = self::DISMISS_GLOBAL;
|
||||
} else {
|
||||
$notices = self::dismissUser($post['id']);
|
||||
$level = self::DISMISS_USER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows developers to apply additional logic to the dismissing notice process.
|
||||
* Executes after updating option or user meta (according to the notice level).
|
||||
*
|
||||
* @param string $notice_id Notice ID (slug).
|
||||
* @param integer $level Notice level.
|
||||
* @param array $notices Dismissed notices.
|
||||
*/
|
||||
do_action('duplicator_admin_notice_dismiss_ajax', $post['id'], $level, $notices);
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'id' => $post['id'],
|
||||
'time' => time(),
|
||||
'level' => $level,
|
||||
'notices' => $notices
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX sub-routine that updates dismissed notices option.
|
||||
*
|
||||
* @param string $id Notice Id.
|
||||
*
|
||||
* @return array Notices.
|
||||
*/
|
||||
private static function dismissGlobal($id)
|
||||
{
|
||||
|
||||
$id = str_replace(self::GLOBAL_PREFIX, '', $id);
|
||||
$notices = get_option(self::DISMISSED_NOTICES_OPTKEY, array());
|
||||
$notices[$id] = array(
|
||||
'time' => time()
|
||||
);
|
||||
|
||||
update_option(self::DISMISSED_NOTICES_OPTKEY, $notices, true);
|
||||
|
||||
return $notices;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX sub-routine that updates dismissed notices user meta.
|
||||
*
|
||||
* @param string $id Notice Id.
|
||||
*
|
||||
* @return array Notices.
|
||||
*/
|
||||
private static function dismissUser($id)
|
||||
{
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$notices = get_user_meta($user_id, self::DISMISSED_NOTICES_OPTKEY, true);
|
||||
$notices = !is_array($notices) ? array() : $notices;
|
||||
$notices[$id] = array(
|
||||
'time' => time()
|
||||
);
|
||||
|
||||
update_user_meta($user_id, self::DISMISSED_NOTICES_OPTKEY, $notices);
|
||||
|
||||
return $notices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete related option
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function deleteOption()
|
||||
{
|
||||
delete_option(self::DISMISSED_NOTICES_OPTKEY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Core\Notifications;
|
||||
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
use Duplicator\Libs\Snap\SnapWP;
|
||||
|
||||
/**
|
||||
* Admin/NoticeBar Education feature for Lite.
|
||||
*/
|
||||
class NoticeBar
|
||||
{
|
||||
/**
|
||||
* Constant for the wp-options key handling the dismissed state
|
||||
*/
|
||||
const NOTICE_BAR_DISMISSED_OPT_KEY = 'duplicator_notice_bar_dismissed';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
add_action('in_admin_header', array(__CLASS__, 'display'));
|
||||
add_action('wp_ajax_duplicator_notice_bar_dismiss', array(__CLASS__, 'dismissNoticeBar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice bar display message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function display()
|
||||
{
|
||||
if (!ControllersManager::isDuplicatorPage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//make sure it wasn't dismissed
|
||||
if (get_user_meta(get_current_user_id(), self::NOTICE_BAR_DISMISSED_OPT_KEY, true) != false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$utm_content = '';
|
||||
foreach (ControllersManager::getMenuLevels() as $key => $value) {
|
||||
if (strlen((string) $value) == 0) {
|
||||
continue;
|
||||
}
|
||||
$utm_content .= ucfirst($key) . ' ' . $value . ' ';
|
||||
}
|
||||
$utm_content = trim($utm_content);
|
||||
|
||||
TplMng::getInstance()->render('/parts/notice-bar', array(
|
||||
'utm_content' => $utm_content
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss notice bar ajax action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function dismissNoticeBar()
|
||||
{
|
||||
// Run a security check.
|
||||
check_ajax_referer('duplicator-notice-bar-dismiss', 'nonce');
|
||||
update_user_meta(get_current_user_id(), self::NOTICE_BAR_DISMISSED_OPT_KEY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete related option
|
||||
*
|
||||
* @return bool true on success, false on failure
|
||||
*/
|
||||
public static function deleteOption()
|
||||
{
|
||||
return SnapWP::deleteUserMetaKey(self::NOTICE_BAR_DISMISSED_OPT_KEY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Core\Notifications;
|
||||
|
||||
use DUP_LITE_Plugin_Upgrade;
|
||||
use DUP_Settings;
|
||||
use Duplicator\Ajax\ServicesNotifications;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
|
||||
/**
|
||||
* Notifications.
|
||||
*/
|
||||
class Notifications
|
||||
{
|
||||
/**
|
||||
* Source of notifications content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const SOURCE_URL = 'https://notifications.duplicator.com/dp-notifications.json';
|
||||
|
||||
/**
|
||||
* WordPress option key containing notification data
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DUPLICATOR_NOTIFICATIONS_OPT_KEY = 'duplicator_notifications';
|
||||
|
||||
/**
|
||||
* WordPress option key containing notification data
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DUPLICATOR_BEFORE_PACKAGES_HOOK = 'duplicator_before_packages_table_action';
|
||||
|
||||
/**
|
||||
* Duplicator notifications dismiss nonce key
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DUPLICATOR_NOTIFICATION_NONCE_KEY = 'duplicator-notification-dismiss';
|
||||
|
||||
/**
|
||||
* Option value.
|
||||
*
|
||||
* @var bool|array
|
||||
*/
|
||||
public static $option = false;
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
// Delete data even if notifications are disabled.
|
||||
add_action('deactivate_plugin', array(__CLASS__, 'delete'), 10, 2);
|
||||
|
||||
if (!DUP_Settings::Get('amNotices')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add notification count to menu label.
|
||||
add_filter('duplicator_menu_label_duplicator', function ($label) {
|
||||
if (self::getCount() === 0) {
|
||||
return $label;
|
||||
}
|
||||
return $label . '<span class="awaiting-mod">' . self::getCount() . '</span>';
|
||||
});
|
||||
|
||||
self::update();
|
||||
|
||||
add_action(self::DUPLICATOR_BEFORE_PACKAGES_HOOK, array(__CLASS__, 'output'));
|
||||
|
||||
$notificationsService = new ServicesNotifications();
|
||||
$notificationsService->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has access and is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasAccess()
|
||||
{
|
||||
return current_user_can('manage_options');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get option value.
|
||||
*
|
||||
* @param bool $cache Reference property cache if available.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getOption($cache = true)
|
||||
{
|
||||
if (self::$option && $cache) {
|
||||
return self::$option;
|
||||
}
|
||||
|
||||
$option = get_option(self::DUPLICATOR_NOTIFICATIONS_OPT_KEY, array());
|
||||
|
||||
self::$option = array(
|
||||
'update' => !empty($option['update']) ? (int)$option['update'] : 0,
|
||||
'feed' => !empty($option['feed']) ? (array)$option['feed'] : array(),
|
||||
'events' => !empty($option['events']) ? (array)$option['events'] : array(),
|
||||
'dismissed' => !empty($option['dismissed']) ? (array)$option['dismissed'] : array()
|
||||
);
|
||||
|
||||
return self::$option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch notifications from feed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fetchFeed()
|
||||
{
|
||||
$response = wp_remote_get(
|
||||
self::SOURCE_URL,
|
||||
array(
|
||||
'timeout' => 10,
|
||||
'user-agent' => self::getUserAgent(),
|
||||
)
|
||||
);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
|
||||
if (empty($body)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return self::verify(json_decode($body, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify notification data before it is saved.
|
||||
*
|
||||
* @param array $notifications Array of notifications items to verify.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function verify($notifications)
|
||||
{
|
||||
$data = array();
|
||||
if (!is_array($notifications) || empty($notifications)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($notifications as $notification) {
|
||||
// Ignore if one of the conditional checks is true:
|
||||
//
|
||||
// 1. notification message is empty.
|
||||
// 2. license type does not match.
|
||||
// 3. notification is expired.
|
||||
// 4. notification has already been dismissed.
|
||||
// 5. notification existed before installing Duplicator.
|
||||
// (Prevents bombarding the user with notifications after activation).
|
||||
if (
|
||||
empty($notification['content']) ||
|
||||
!self::isLicenseTypeMatch($notification) ||
|
||||
self::isExpired($notification) ||
|
||||
self::isDismissed($notification) ||
|
||||
self::isExisted($notification)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[] = $notification;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify saved notification data for active notifications.
|
||||
*
|
||||
* @param array $notifications Array of notifications items to verify.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function verifyActive($notifications)
|
||||
{
|
||||
if (!is_array($notifications) || empty($notifications)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$current_timestamp = time();
|
||||
|
||||
// Remove notifications that are not active.
|
||||
foreach ($notifications as $key => $notification) {
|
||||
if (
|
||||
(!empty($notification['start']) && $current_timestamp < strtotime($notification['start'])) ||
|
||||
(!empty($notification['end']) && $current_timestamp > strtotime($notification['end']))
|
||||
) {
|
||||
unset($notifications[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get()
|
||||
{
|
||||
if (!self::hasAccess()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$option = self::getOption();
|
||||
|
||||
$feed = !empty($option['feed']) ? self::verifyActive($option['feed']) : array();
|
||||
$events = !empty($option['events']) ? self::verifyActive($option['events']) : array();
|
||||
|
||||
return array_merge($feed, $events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification count.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getCount()
|
||||
{
|
||||
return count(self::get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Event Driven notification.
|
||||
*
|
||||
* @param array $notification Notification data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function add($notification)
|
||||
{
|
||||
if (!self::isValid($notification)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$option = self::getOption();
|
||||
|
||||
// Notification ID already exists.
|
||||
if (!empty($option['events'][$notification['id']])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notification = self::verify(array($notification));
|
||||
update_option(
|
||||
self::DUPLICATOR_NOTIFICATIONS_OPT_KEY,
|
||||
array(
|
||||
'update' => $option['update'],
|
||||
'feed' => $option['feed'],
|
||||
'events' => array_merge($notification, $option['events']),
|
||||
'dismissed' => $option['dismissed'],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if notification data is valid.
|
||||
*
|
||||
* @param array $notification Notification data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValid($notification)
|
||||
{
|
||||
if (empty($notification['id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return count(self::verify(array($notification))) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if notification has already been dismissed.
|
||||
*
|
||||
* @param array $notification Notification data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isDismissed($notification)
|
||||
{
|
||||
$option = self::getOption();
|
||||
|
||||
return !empty($option['dismissed']) && in_array($notification['id'], $option['dismissed']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if license type is match.
|
||||
*
|
||||
* @param array $notification Notification data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isLicenseTypeMatch($notification)
|
||||
{
|
||||
// A specific license type is not required.
|
||||
$notification['type'] = (array)$notification['type'];
|
||||
if (empty($notification['type'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array('any', $notification['type'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array(self::getLicenseType(), (array)$notification['type'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if notification is expired.
|
||||
*
|
||||
* @param array $notification Notification data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isExpired($notification)
|
||||
{
|
||||
return !empty($notification['end']) && time() > strtotime($notification['end']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if notification existed before installing Duplicator.
|
||||
*
|
||||
* @param array $notification Notification data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isExisted($notification)
|
||||
{
|
||||
$installInfo = DUP_LITE_Plugin_Upgrade::getInstallInfo();
|
||||
return (!empty($notification['start']) && $installInfo['time'] > strtotime($notification['start']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update notification data from feed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update()
|
||||
{
|
||||
$option = self::getOption();
|
||||
|
||||
//Only update twice daily
|
||||
if ($option['update'] !== 0 && time() < $option['update'] + DAY_IN_SECONDS / 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'update' => time(),
|
||||
'feed' => self::fetchFeed(),
|
||||
'events' => $option['events'],
|
||||
'dismissed' => $option['dismissed'],
|
||||
);
|
||||
|
||||
/**
|
||||
* Allow changing notification data before it will be updated in database.
|
||||
*
|
||||
* @param array $data New notification data.
|
||||
*/
|
||||
$data = (array)apply_filters('duplicator_admin_notifications_update_data', $data);
|
||||
|
||||
update_option(self::DUPLICATOR_NOTIFICATIONS_OPT_KEY, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove notification data from database before a plugin is deactivated.
|
||||
*
|
||||
* @param string $plugin Path to the plugin file relative to the plugins directory.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function delete($plugin)
|
||||
{
|
||||
$duplicator_plugins = array(
|
||||
'duplicator-lite/duplicator.php',
|
||||
'duplicator/duplicator.php',
|
||||
);
|
||||
|
||||
if (!in_array($plugin, $duplicator_plugins, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete_option(self::DUPLICATOR_NOTIFICATIONS_OPT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets on Form Overview admin page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueues()
|
||||
{
|
||||
if (!self::getCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style(
|
||||
'dup-admin-notifications',
|
||||
DUPLICATOR_PLUGIN_URL . "assets/css/admin-notifications.css",
|
||||
array('dup-lity'),
|
||||
DUPLICATOR_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'dup-admin-notifications',
|
||||
DUPLICATOR_PLUGIN_URL . "assets/js/notifications/admin-notifications.js",
|
||||
array('jquery', 'dup-lity'),
|
||||
DUPLICATOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'dup-admin-notifications',
|
||||
'dup_admin_notifications',
|
||||
array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce(self::DUPLICATOR_NOTIFICATION_NONCE_KEY),
|
||||
)
|
||||
);
|
||||
|
||||
// Lity.
|
||||
wp_enqueue_style(
|
||||
'dup-lity',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/lib/lity/lity.min.css',
|
||||
array(),
|
||||
DUPLICATOR_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'dup-lity',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/lib/lity/lity.min.js',
|
||||
array('jquery'),
|
||||
DUPLICATOR_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output notifications on Form Overview admin area.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function output()
|
||||
{
|
||||
$notificationsData = self::get();
|
||||
|
||||
if (empty($notificationsData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$content_allowed_tags = array(
|
||||
'br' => array(),
|
||||
'em' => array(),
|
||||
'strong' => array(),
|
||||
'span' => array(
|
||||
'style' => array()
|
||||
),
|
||||
'p' => array(
|
||||
'id' => array(),
|
||||
'class' => array()
|
||||
),
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'target' => array(),
|
||||
'rel' => array()
|
||||
)
|
||||
);
|
||||
|
||||
$notifications = array();
|
||||
foreach ($notificationsData as $notificationData) {
|
||||
// Prepare required arguments.
|
||||
$notificationData = wp_parse_args(
|
||||
$notificationData,
|
||||
array(
|
||||
'id' => 0,
|
||||
'title' => '',
|
||||
'content' => '',
|
||||
'video' => ''
|
||||
)
|
||||
);
|
||||
|
||||
$title = self::getComponentData($notificationData['title']);
|
||||
$content = self::getComponentData($notificationData['content']);
|
||||
|
||||
if (!$title && !$content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notifications[] = array(
|
||||
'id' => $notificationData['id'],
|
||||
'title' => $title,
|
||||
'btns' => self::getButtonsData($notificationData),
|
||||
'content' => wp_kses(wpautop($content), $content_allowed_tags),
|
||||
'video_url' => wp_http_validate_url(self::getComponentData($notificationData['video'])),
|
||||
);
|
||||
}
|
||||
|
||||
self::enqueues();
|
||||
TplMng::getInstance()->render(
|
||||
'parts/Notifications/main',
|
||||
array(
|
||||
'notifications' => $notifications
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve notification's buttons.
|
||||
*
|
||||
* @param array $notification Notification data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function getButtonsData($notification)
|
||||
{
|
||||
if (empty($notification['btn']) || !is_array($notification['btn'])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$buttons = array();
|
||||
if (!empty($notification['btn']['main_text']) && !empty($notification['btn']['main_url'])) {
|
||||
$buttons[] = array(
|
||||
'type' => 'primary',
|
||||
'text' => $notification['btn']['main_text'],
|
||||
'url' => self::prepareBtnUrl($notification['btn']['main_url']),
|
||||
'target' => '_blank'
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($notification['btn']['alt_text']) && !empty($notification['btn']['alt_url'])) {
|
||||
$buttons[] = array(
|
||||
'type' => 'secondary',
|
||||
'text' => $notification['btn']['alt_text'],
|
||||
'url' => self::prepareBtnUrl($notification['btn']['alt_url']),
|
||||
'target' => '_blank'
|
||||
);
|
||||
}
|
||||
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve notification's component data by a license type.
|
||||
*
|
||||
* @param mixed $data Component data.
|
||||
*
|
||||
* @return false|mixed
|
||||
*/
|
||||
private static function getComponentData($data)
|
||||
{
|
||||
if (empty($data['license'])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$license_type = self::getLicenseType();
|
||||
return !empty($data['license'][$license_type]) ? $data['license'][$license_type] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current installation license type (always lowercase).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getLicenseType()
|
||||
{
|
||||
return 'lite';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare button URL.
|
||||
*
|
||||
* @param string $btnUrl Button url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function prepareBtnUrl($btnUrl)
|
||||
{
|
||||
if (empty($btnUrl)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$replace_tags = array(
|
||||
'{admin_url}' => admin_url()
|
||||
);
|
||||
|
||||
return wp_http_validate_url(str_replace(array_keys($replace_tags), array_values($replace_tags), $btnUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* User agent that will be used for the request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getUserAgent()
|
||||
{
|
||||
return 'WordPress/' . get_bloginfo('version') . '; ' . get_bloginfo('url') . '; Duplicator/Lite-' . DUPLICATOR_VERSION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Core\Notifications;
|
||||
|
||||
use DUP_LITE_Plugin_Upgrade;
|
||||
use DUP_Package;
|
||||
use Duplicator\Core\MigrationMng;
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
|
||||
/**
|
||||
* Ask for some love.
|
||||
*/
|
||||
class Review
|
||||
{
|
||||
/**
|
||||
* Constant for the review request admin notice slug
|
||||
*/
|
||||
const REVIEW_REQUEST_NOTICE_SLUG = 'review_request';
|
||||
|
||||
/**
|
||||
* Primary class constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
|
||||
// Admin notice requesting review.
|
||||
add_action('admin_init', array(__CLASS__, 'reviewRequest'));
|
||||
|
||||
// Admin footer text.
|
||||
add_filter('admin_footer_text', array(__CLASS__, 'adminFooter'), 1, 2);
|
||||
|
||||
// Admin footer version text.
|
||||
add_filter('update_footer', array(__CLASS__, 'adminFooterVersion'), 9999);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin notices as needed for reviews.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reviewRequest()
|
||||
{
|
||||
|
||||
// Only consider showing the review request to admin users.
|
||||
if (!is_super_admin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get dismissed notices.
|
||||
$notices = get_option(Notice::DISMISSED_NOTICES_OPTKEY, array());
|
||||
|
||||
//has already been dismissed, don't show again
|
||||
if (isset($notices[self::REVIEW_REQUEST_NOTICE_SLUG])) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::reviewLite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe show Lite review request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reviewLite()
|
||||
{
|
||||
$display = false;
|
||||
|
||||
// Fetch when plugin was initially installed.
|
||||
$installInfo = DUP_LITE_Plugin_Upgrade::getInstallInfo();
|
||||
$numberOfPackages = DUP_Package::count_by_status(array(
|
||||
array('op' => '=' , 'status' => \DUP_PackageStatus::COMPLETE )
|
||||
));
|
||||
|
||||
// Display if plugin has been installed for at least 3 days and has a package installed
|
||||
if ((($installInfo['time'] + (DAY_IN_SECONDS * 3)) < time() && $numberOfPackages > 0)) {
|
||||
$display = true;
|
||||
}
|
||||
|
||||
//Display if it's been 3 days after a successful migration
|
||||
$migrationTime = MigrationMng::getMigrationData('time');
|
||||
if (!$display && $migrationTime !== false && (($migrationTime + (DAY_IN_SECONDS * 3)) < time())) {
|
||||
$display = true;
|
||||
}
|
||||
|
||||
if (!$display) {
|
||||
return;
|
||||
}
|
||||
|
||||
Notice::addMultistep(
|
||||
array(
|
||||
array(
|
||||
"message" => "<p>" . sprintf(__('Are you enjoying %s?', 'duplicator'), 'Duplicator') . "</p>",
|
||||
"links" => array(
|
||||
array(
|
||||
"text" => __('Yes', 'duplicator'),
|
||||
"switch" => 1
|
||||
),
|
||||
array(
|
||||
"text" => __('Not really', 'duplicator'),
|
||||
"switch" => 2
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
"message" => "<p>" .
|
||||
__(
|
||||
'That’s awesome! Could you please do me a BIG favor and give it a 5-star rating on ' .
|
||||
'WordPress to help us spread the word and boost our motivation?',
|
||||
'duplicator'
|
||||
) . "</p>" .
|
||||
"<p>" . wp_kses(__('~ John Turner<br>President of Duplicator', 'duplicator'), array('br' => array())) . "</p>",
|
||||
"links" => array(
|
||||
array(
|
||||
"url" => self::getReviewUrl(),
|
||||
"text" => __('Ok, you deserve it', 'duplicator'),
|
||||
"dismiss" => true
|
||||
),
|
||||
array(
|
||||
"text" => __('Nope, maybe later', 'duplicator'),
|
||||
"dismiss" => true
|
||||
),
|
||||
array(
|
||||
"text" => __('I already did', 'duplicator'),
|
||||
"dismiss" => true
|
||||
)
|
||||
)
|
||||
),
|
||||
array(
|
||||
"message" => "<p>" .
|
||||
__(
|
||||
'We\'re sorry to hear you aren\'t enjoying Duplicator. We would love a chance to improve. ' .
|
||||
'Could you take a minute and let us know what we can do better?',
|
||||
'duplicator'
|
||||
) . "</p>",
|
||||
"links" => array(
|
||||
array(
|
||||
"url" => self::getFeedbackUrl(),
|
||||
"text" => __('Give Feedback', 'duplicator'),
|
||||
"dismiss" => true
|
||||
),
|
||||
array(
|
||||
"text" => __('No thanks', 'duplicator'),
|
||||
"dismiss" => true
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
self::REVIEW_REQUEST_NOTICE_SLUG,
|
||||
Notice::NOTICE_TYPE_INFO,
|
||||
array(
|
||||
'dismiss' => Notice::DISMISS_GLOBAL,
|
||||
'autop' => false,
|
||||
'class' => 'dup-review-notice',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The review url on wordpress.org
|
||||
*/
|
||||
public static function getReviewUrl()
|
||||
{
|
||||
return "https://wordpress.org/support/plugin/duplicator/reviews/#new-post";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The snapcreek feedback url
|
||||
*/
|
||||
public static function getFeedbackUrl()
|
||||
{
|
||||
return DUPLICATOR_BLOG_URL . "contact/";
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates admin footer text by adding Duplicator version
|
||||
*
|
||||
* @param string $defaultText Default WP footer text
|
||||
*
|
||||
* @return string Modified version text
|
||||
*/
|
||||
public static function adminFooterVersion($defaultText)
|
||||
{
|
||||
if (!ControllersManager::isDuplicatorPage()) {
|
||||
return $defaultText;
|
||||
}
|
||||
|
||||
$defaultText = sprintf(
|
||||
'%1$s | Duplicator %2$s',
|
||||
$defaultText,
|
||||
esc_html(DUPLICATOR_VERSION)
|
||||
);
|
||||
|
||||
return $defaultText;
|
||||
}
|
||||
|
||||
/**
|
||||
* When user is on a Duplicator related admin page, display footer text
|
||||
* that graciously asks them to rate us.
|
||||
*
|
||||
* @param string $text Footer text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function adminFooter($text)
|
||||
{
|
||||
//Show only on duplicator pages
|
||||
if (
|
||||
! is_admin() ||
|
||||
empty($_REQUEST['page']) ||
|
||||
strpos($_REQUEST['page'], 'duplicator') === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$text = sprintf(
|
||||
wp_kses( /* translators: $1$s - WPForms plugin name; $2$s - WP.org review link; $3$s - WP.org review link. */
|
||||
__(
|
||||
'Please rate <strong>Duplicator</strong> ' .
|
||||
'<a href="%1$s" target="_blank" rel="noopener noreferrer">★★★★★</a>' .
|
||||
' on <a href="%1$s" target="_blank" rel="noopener">WordPress.org</a> to help us spread the word. Thank you from the Duplicator team!',
|
||||
'duplicator'
|
||||
),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'target' => array(),
|
||||
'rel' => array(),
|
||||
),
|
||||
'strong' => array()
|
||||
)
|
||||
),
|
||||
self::getReviewUrl()
|
||||
);
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
42
html/wp-content/plugins/duplicator/src/Core/Unistall.php
Normal file
42
html/wp-content/plugins/duplicator/src/Core/Unistall.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Interface that collects the functions of initial duplicator Bootstrap
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Core;
|
||||
|
||||
/**
|
||||
* Uninstall class
|
||||
*/
|
||||
class Unistall
|
||||
{
|
||||
/**
|
||||
* Registrer unistall hoosk
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function registerHooks()
|
||||
{
|
||||
if (is_admin()) {
|
||||
register_deactivation_hook(DUPLICATOR_LITE_FILE, array(__CLASS__, 'deactivate'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivation Hook:
|
||||
* Hooked into `register_deactivation_hook`. Routines used to deactivate the plugin
|
||||
* For uninstall see uninstall.php WordPress by default will call the uninstall.php file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function deactivate()
|
||||
{
|
||||
MigrationMng::renameInstallersPhpFiles();
|
||||
|
||||
do_action('duplicator_after_deactivation');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Core\Upgrade;
|
||||
|
||||
use DUP_Settings;
|
||||
use Duplicator\Utils\Email\EmailSummary;
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use DUP_Log;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Utility class managing actions when the plugin is updated
|
||||
*/
|
||||
class UpgradeFunctions
|
||||
{
|
||||
const LAST_VERSION_EMAIL_SUMMARY_WRONG_KEY = '1.5.6.1';
|
||||
const FIRST_VERSION_NEW_STORAGE_POSITION = '1.3.35';
|
||||
const FIRST_VERSION_FOLDER_MIGRATION = '1.5.14';
|
||||
const FIRST_VERSION_WITH_LOGS_SUBFOLDER = '1.5.14-beta1';
|
||||
|
||||
/**
|
||||
* This function is executed when the plugin is activated and
|
||||
* every time the version saved in the wp_options is different from the plugin version both in upgrade and downgrade.
|
||||
*
|
||||
* @param false|string $currentVersion current Duplicator version, false if is first installation
|
||||
* @param string $newVersion new Duplicator Version
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function performUpgrade($currentVersion, $newVersion): void
|
||||
{
|
||||
self::updateStoragePostition($currentVersion);
|
||||
self::emailSummaryOptKeyUpdate($currentVersion);
|
||||
self::migrateStorageFolders($currentVersion, $newVersion);
|
||||
self::migrateLogsToSubfolder($currentVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update email summary option key seperator from '-' to '_'
|
||||
*
|
||||
* @param false|string $currentVersion current Duplicator version, false if is first installation
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function emailSummaryOptKeyUpdate($currentVersion): void
|
||||
{
|
||||
if ($currentVersion == false || version_compare($currentVersion, self::LAST_VERSION_EMAIL_SUMMARY_WRONG_KEY, '>')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (($data = get_option(EmailSummary::INFO_OPT_OLD_KEY)) !== false) {
|
||||
update_option(EmailSummary::INFO_OPT_KEY, $data);
|
||||
delete_option(EmailSummary::INFO_OPT_OLD_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update storage position option
|
||||
*
|
||||
* @param false|string $currentVersion current Duplicator version, false if is first installation
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function updateStoragePostition($currentVersion): void
|
||||
{
|
||||
//PRE 1.3.35
|
||||
//Do not update to new wp-content storage till after
|
||||
if ($currentVersion !== false && version_compare($currentVersion, self::FIRST_VERSION_NEW_STORAGE_POSITION, '<')) {
|
||||
DUP_Settings::Set('storage_position', DUP_Settings::STORAGE_POSITION_LEGACY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate storage folders from legacy to new location
|
||||
*
|
||||
* @param false|string $currentVersion current Duplicator version, false if first install
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function migrateStorageFolders($currentVersion): void
|
||||
{
|
||||
// Skip on fresh installs or if already past migration version
|
||||
if ($currentVersion === false || version_compare($currentVersion, self::FIRST_VERSION_FOLDER_MIGRATION, '>=')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If storage position is already set to new, do not migrate
|
||||
if (DUP_Settings::Get('storage_position') === DUP_Settings::STORAGE_POSITION_WP_CONTENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force using wp-content storage position
|
||||
DUP_Settings::setStoragePosition(DUP_Settings::STORAGE_POSITION_WP_CONTENT);
|
||||
DUP_Settings::Save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate existing log files from root directory to logs subfolder
|
||||
*
|
||||
* @param false|string $currentVersion current Duplicator version, false if is first installation
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function migrateLogsToSubfolder($currentVersion): void
|
||||
{
|
||||
if ($currentVersion === false || version_compare($currentVersion, self::FIRST_VERSION_WITH_LOGS_SUBFOLDER, '>=')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DUP_Log::Trace("MIGRATION: Moving log files to logs subfolder");
|
||||
|
||||
// Ensure logs directory exists
|
||||
if (!file_exists(DUP_Settings::getSsdirLogsPath())) {
|
||||
SnapIO::dirWriteCheckOrMkdir(DUP_Settings::getSsdirLogsPath(), 'u+rwx');
|
||||
}
|
||||
|
||||
// Use SnapIO::regexGlob for more robust file discovery
|
||||
$logFiles = SnapIO::regexGlob(DUP_Settings::getSsdirPath(), [
|
||||
'regexFile' => [
|
||||
'/.*\.log$/',
|
||||
'/.*\.log1$/',
|
||||
],
|
||||
'regexFolder' => false,
|
||||
'recursive' => false,
|
||||
]);
|
||||
|
||||
$migratedCount = 0;
|
||||
foreach ($logFiles as $oldPath) {
|
||||
$filename = basename($oldPath);
|
||||
$newPath = DUP_Settings::getSsdirLogsPath() . '/' . $filename;
|
||||
|
||||
if (SnapIO::rename($oldPath, $newPath)) {
|
||||
$migratedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
DUP_Log::Trace("MIGRATION: Moved {$migratedCount} log files to logs subfolder - old location is now clean");
|
||||
} catch (Exception $e) {
|
||||
DUP_Log::Trace("MIGRATION: Error moving log files: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
244
html/wp-content/plugins/duplicator/src/Core/Views/TplMng.php
Normal file
244
html/wp-content/plugins/duplicator/src/Core/Views/TplMng.php
Normal file
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Template view manager
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Core\Views;
|
||||
|
||||
use Duplicator\Libs\Snap\SnapJson;
|
||||
|
||||
final class TplMng
|
||||
{
|
||||
/** @var ?self */
|
||||
private static $instance = null;
|
||||
/** @var string */
|
||||
private $mainFolder = '';
|
||||
/** @var bool */
|
||||
private static $stripSpaces = false;
|
||||
/** @var mixed[] */
|
||||
private $globalData = array();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
$this->mainFolder = DUPLICATOR_PLUGIN_PATH . '/template/';
|
||||
}
|
||||
|
||||
/**
|
||||
* If strip spaces is true in render method spaced between tag are removed
|
||||
*
|
||||
* @param bool $strip if true strip spaces
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setStripSpaces($strip)
|
||||
{
|
||||
self::$stripSpaces = (bool) $strip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set template global value in template data
|
||||
*
|
||||
* @param string $key global value key
|
||||
* @param mixed $val value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setGlobalValue($key, $val)
|
||||
{
|
||||
$this->globalData[$key] = $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove global value if exist
|
||||
*
|
||||
* @param string $key gloval value key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unsetGlobalValue($key)
|
||||
{
|
||||
if (isset($this->globalData[$key])) {
|
||||
unset($this->globalData[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if global values exists
|
||||
*
|
||||
* @param string $key gloval value key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasGlobalValue($key)
|
||||
{
|
||||
return isset($this->globalData[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple global data set
|
||||
*
|
||||
* @param array<string, mixed> $data data tu set in global data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateGlobalData(array $data = array())
|
||||
{
|
||||
$this->globalData = array_merge($this->globalData, (array) $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return global data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getGlobalData()
|
||||
{
|
||||
return $this->globalData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render template
|
||||
*
|
||||
* @param string $slugTpl template file is a relative path from root template folder
|
||||
* @param array<string, mixed> $args array key / val where key is the var name in template
|
||||
* @param bool $echo if false return template in string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render($slugTpl, $args = array(), $echo = true)
|
||||
{
|
||||
ob_start();
|
||||
if (($renderFile = $this->getFileTemplate($slugTpl)) !== false) {
|
||||
$tplData = apply_filters(self::getDataHook($slugTpl), array_merge($this->globalData, $args));
|
||||
$tplMng = $this;
|
||||
require($renderFile);
|
||||
} else {
|
||||
echo '<p>FILE TPL NOT FOUND: ' . $slugTpl . '</p>';
|
||||
}
|
||||
$renderResult = apply_filters(self::getRenderHook($slugTpl), ob_get_clean());
|
||||
|
||||
if (self::$stripSpaces) {
|
||||
$renderResult = preg_replace('~>[\n\s]+<~', '><', $renderResult);
|
||||
}
|
||||
if ($echo) {
|
||||
echo $renderResult;
|
||||
return '';
|
||||
} else {
|
||||
return $renderResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render template in json string
|
||||
*
|
||||
* @param string $slugTpl template file is a relative path from root template folder
|
||||
* @param array<string, mixed> $args array key / val where key is the var name in template
|
||||
* @param bool $echo if false return template in string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderJson($slugTpl, $args = array(), $echo = true)
|
||||
{
|
||||
$renderResult = SnapJson::jsonEncode($this->render($slugTpl, $args, false));
|
||||
if ($echo) {
|
||||
echo $renderResult;
|
||||
return '';
|
||||
} else {
|
||||
return $renderResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render template apply esc attr
|
||||
*
|
||||
* @param string $slugTpl template file is a relative path from root template folder
|
||||
* @param array<string, mixed> $args array key / val where key is the var name in template
|
||||
* @param bool $echo if false return template in string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderEscAttr($slugTpl, $args = array(), $echo = true)
|
||||
{
|
||||
$renderResult = esc_attr($this->render($slugTpl, $args, false));
|
||||
if ($echo) {
|
||||
echo $renderResult;
|
||||
return '';
|
||||
} else {
|
||||
return $renderResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hook unique from template slug
|
||||
*
|
||||
* @param string $slugTpl template slug
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function tplFileToHookSlug($slugTpl)
|
||||
{
|
||||
return str_replace(array('\\', '/', '.'), '_', $slugTpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data hook from template slug
|
||||
*
|
||||
* @param string $slugTpl template slug
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDataHook($slugTpl)
|
||||
{
|
||||
return 'duplicator_template_data_' . self::tplFileToHookSlug($slugTpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return render hook from template slug
|
||||
*
|
||||
* @param string $slugTpl template slug
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getRenderHook($slugTpl)
|
||||
{
|
||||
return 'duplicator_template_render_' . self::tplFileToHookSlug($slugTpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acctept html of php extensions. if the file have unknown extension automatic add the php extension
|
||||
*
|
||||
* @param string $slugTpl template slug
|
||||
*
|
||||
* @return boolean|string return false if don\'t find the template file
|
||||
*/
|
||||
protected function getFileTemplate($slugTpl)
|
||||
{
|
||||
$fullPath = $this->mainFolder . $slugTpl . '.php';
|
||||
|
||||
if (file_exists($fullPath)) {
|
||||
return $fullPath;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
3232
html/wp-content/plugins/duplicator/src/Libs/Certificates/cacert.pem
Normal file
3232
html/wp-content/plugins/duplicator/src/Libs/Certificates/cacert.pem
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Libs\DupArchive;
|
||||
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderDirectoryHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderFileHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderGlobHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderHeader;
|
||||
use Error;
|
||||
use Exception;
|
||||
|
||||
class DupArchive
|
||||
{
|
||||
const DUPARCHIVE_VERSION = '1.0.0';
|
||||
const INDEX_FILE_NAME = '__dup__archive__index.json';
|
||||
const INDEX_FILE_SIZE = 2000; // reserver 2K
|
||||
const EXTRA_FILES_POS_KEY = 'extraPos';
|
||||
|
||||
const HEADER_TYPE_NONE = 0;
|
||||
const HEADER_TYPE_FILE = 1;
|
||||
const HEADER_TYPE_DIR = 2;
|
||||
const HEADER_TYPE_GLOB = 3;
|
||||
|
||||
/**
|
||||
* Get header type enum
|
||||
*
|
||||
* @param resource $archiveHandle archive resource
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected static function getNextHeaderType($archiveHandle)
|
||||
{
|
||||
$retVal = self::HEADER_TYPE_NONE;
|
||||
$marker = fgets($archiveHandle, 4);
|
||||
|
||||
if (feof($archiveHandle) === false) {
|
||||
switch ($marker) {
|
||||
case '<D>':
|
||||
$retVal = self::HEADER_TYPE_DIR;
|
||||
break;
|
||||
case '<F>':
|
||||
$retVal = self::HEADER_TYPE_FILE;
|
||||
break;
|
||||
case '<G>':
|
||||
$retVal = self::HEADER_TYPE_GLOB;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid header marker {$marker}. Location:" . ftell($archiveHandle));
|
||||
}
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get archive index data
|
||||
*
|
||||
* @param string $archivePath archive path
|
||||
*
|
||||
* @return bool|array return index data, false if don't exists
|
||||
*/
|
||||
public static function getIndexData($archivePath)
|
||||
{
|
||||
try {
|
||||
$indexContent = self::getSrcFile($archivePath, self::INDEX_FILE_NAME, 0, 3000, false);
|
||||
if ($indexContent === false) {
|
||||
return false;
|
||||
}
|
||||
$indexData = json_decode(rtrim($indexContent, "\0"), true);
|
||||
|
||||
if (!is_array($indexData)) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
} catch (Error $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $indexData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra files offset if set or 0
|
||||
*
|
||||
* @param string $archivePath archive path
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getExtraOffset($archivePath)
|
||||
{
|
||||
if (($indexData = self::getIndexData($archivePath)) === false) {
|
||||
return 0;
|
||||
}
|
||||
return (isset($indexData[self::EXTRA_FILES_POS_KEY]) ? $indexData[self::EXTRA_FILES_POS_KEY] : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file in archive from src
|
||||
*
|
||||
* @param string $archivePath archive path
|
||||
* @param string $relativePath relative path
|
||||
* @param int $offset start search location
|
||||
* @param int $sizeToSearch max size where search
|
||||
*
|
||||
* @return bool|int false if file not found of path position
|
||||
*/
|
||||
public static function seachPathInArchive($archivePath, $relativePath, $offset = 0, $sizeToSearch = 0)
|
||||
{
|
||||
if (($archiveHandle = fopen($archivePath, 'rb')) === false) {
|
||||
throw new Exception("Can’t open archive at $archivePath!");
|
||||
}
|
||||
$result = self::searchPath($archivePath, $relativePath, $offset, $sizeToSearch);
|
||||
@fclose($archiveHandle);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search path, if found set and return position
|
||||
*
|
||||
* @param resource $archiveHandle dup archive resource
|
||||
* @param string $relativePath relative path to extract
|
||||
* @param int $offset start search location
|
||||
* @param int $sizeToSearch max size where search
|
||||
*
|
||||
* @return bool|int false if file not found of path position
|
||||
*/
|
||||
public static function searchPath($archiveHandle, $relativePath, $offset = 0, $sizeToSearch = 0)
|
||||
{
|
||||
if (!is_resource($archiveHandle)) {
|
||||
throw new Exception('Archive handle must be a resource');
|
||||
}
|
||||
|
||||
if (fseek($archiveHandle, $offset, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($offset == 0) {
|
||||
DupArchiveReaderHeader::readFromArchive($archiveHandle);
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$position = ftell($archiveHandle);
|
||||
$continue = true;
|
||||
|
||||
do {
|
||||
switch (($type = self::getNextHeaderType($archiveHandle))) {
|
||||
case self::HEADER_TYPE_FILE:
|
||||
$currentFileHeader = DupArchiveReaderFileHeader::readFromArchive($archiveHandle, true, true);
|
||||
if ($currentFileHeader->relativePath == $relativePath) {
|
||||
$continue = false;
|
||||
$result = $position;
|
||||
}
|
||||
break;
|
||||
case self::HEADER_TYPE_DIR:
|
||||
$directoryHeader = DupArchiveReaderDirectoryHeader::readFromArchive($archiveHandle, true);
|
||||
if ($directoryHeader->relativePath == $relativePath) {
|
||||
$continue = false;
|
||||
$result = $position;
|
||||
}
|
||||
break;
|
||||
case self::HEADER_TYPE_NONE:
|
||||
$continue = false;
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invali header type "' . $type . '"');
|
||||
}
|
||||
$position = ftell($archiveHandle);
|
||||
if ($sizeToSearch > 0 && ($position - $offset) >= $sizeToSearch) {
|
||||
break;
|
||||
}
|
||||
} while ($continue);
|
||||
|
||||
if ($result !== false) {
|
||||
if (fseek($archiveHandle, $result, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file content
|
||||
*
|
||||
* @param string $archivePath archvie path
|
||||
* @param string $relativePath relative path to extract
|
||||
* @param int $offset start search location
|
||||
* @param int $sizeToSearch max size where search
|
||||
* @param bool $isCompressed true if is compressed
|
||||
*
|
||||
* @return bool|string false if file not found
|
||||
*/
|
||||
public static function getSrcFile($archivePath, $relativePath, $offset = 0, $sizeToSearch = 0, $isCompressed = null)
|
||||
{
|
||||
if (($archiveHandle = fopen($archivePath, 'rb')) === false) {
|
||||
throw new Exception("Can’t open archive at $archivePath!");
|
||||
}
|
||||
$archiveHeader = DupArchiveReaderHeader::readFromArchive($archiveHandle);
|
||||
if (is_null($isCompressed)) {
|
||||
$isCompressed = $archiveHeader->isCompressed;
|
||||
}
|
||||
|
||||
if (self::searchPath($archiveHandle, $relativePath, $offset, $sizeToSearch) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self::getNextHeaderType($archiveHandle) != self::HEADER_TYPE_FILE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$header = DupArchiveReaderFileHeader::readFromArchive($archiveHandle, false, true);
|
||||
$result = self::getSrcFromHeader($archiveHandle, $header, $isCompressed);
|
||||
@fclose($archiveHandle);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get src file form header
|
||||
*
|
||||
* @param resource $archiveHandle archive handle
|
||||
* @param DupArchiveReaderFileHeader $fileHeader file header
|
||||
* @param bool $isCompressed true if is compressed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getSrcFromHeader($archiveHandle, DupArchiveReaderFileHeader $fileHeader, $isCompressed)
|
||||
{
|
||||
if ($fileHeader->fileSize == 0) {
|
||||
return '';
|
||||
}
|
||||
$dataSize = 0;
|
||||
$result = '';
|
||||
|
||||
do {
|
||||
$globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle);
|
||||
$result .= DupArchiveReaderGlobHeader::readContent($archiveHandle, $globHeader, $isCompressed);
|
||||
$dataSize += $globHeader->originalSize;
|
||||
} while ($dataSize < $fileHeader->fileSize);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip file in archive
|
||||
*
|
||||
* @param resource $archiveHandle dup archive resource
|
||||
* @param DupArchiveFileHeader $fileHeader file header
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function skipFileInArchive($archiveHandle, DupArchiveReaderFileHeader $fileHeader)
|
||||
{
|
||||
if ($fileHeader->fileSize == 0) {
|
||||
return;
|
||||
}
|
||||
$dataSize = 0;
|
||||
|
||||
do {
|
||||
$globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle, true);
|
||||
$dataSize += $globHeader->originalSize;
|
||||
} while ($dataSize < $fileHeader->fileSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes we are on one header and just need to get to the next
|
||||
*
|
||||
* @param resource $archiveHandle dup archive resource
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function skipToNextHeader($archiveHandle)
|
||||
{
|
||||
$headerType = self::getNextHeaderType($archiveHandle);
|
||||
switch ($headerType) {
|
||||
case self::HEADER_TYPE_FILE:
|
||||
$fileHeader = DupArchiveReaderFileHeader::readFromArchive($archiveHandle, false, true);
|
||||
self::skipFileInArchive($archiveHandle, $fileHeader);
|
||||
break;
|
||||
case self::HEADER_TYPE_DIR:
|
||||
DupArchiveReaderDirectoryHeader::readFromArchive($archiveHandle, true);
|
||||
break;
|
||||
case self::HEADER_TYPE_NONE:
|
||||
false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,827 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive;
|
||||
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveDirectoryHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveFileHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderFileHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderGlobHeader;
|
||||
use Duplicator\Libs\DupArchive\Info\DupArchiveInfo;
|
||||
use Duplicator\Libs\DupArchive\Processors\DupArchiveDirectoryProcessor;
|
||||
use Duplicator\Libs\DupArchive\Processors\DupArchiveFileProcessor;
|
||||
use Duplicator\Libs\DupArchive\Processors\DupArchiveProcessingFailure;
|
||||
use Duplicator\Libs\DupArchive\States\DupArchiveCreateState;
|
||||
use Duplicator\Libs\DupArchive\States\DupArchiveExpandState;
|
||||
use Duplicator\Libs\DupArchive\States\DupArchiveSimpleCreateState;
|
||||
use Duplicator\Libs\DupArchive\States\DupArchiveSimpleExpandState;
|
||||
use Duplicator\Libs\DupArchive\Utils\DupArchiveScanUtil;
|
||||
use Duplicator\Libs\DupArchive\Utils\DupArchiveUtil;
|
||||
use Duplicator\Libs\Snap\Snap32BitSizeLimitException;
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use Exception;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* $re = '/\/\/.* /';
|
||||
* $subst = '';
|
||||
*
|
||||
* $re = '/(.*^\s*)(namespace.*?)(;)(.*)/sm';
|
||||
* $subst = '$2 { $4}';
|
||||
*
|
||||
* $re = '/\/\*.*?\*\//s'; ''
|
||||
* $re = '/\n\s*\n/s'; "\n"
|
||||
*/
|
||||
class DupArchiveEngine extends DupArchive
|
||||
{
|
||||
const EXCEPTION_NON_FATAL = 0;
|
||||
const EXCEPTION_FATAL = 1;
|
||||
|
||||
/** @var string|null */
|
||||
public static $targetRootPath = null;
|
||||
|
||||
/**
|
||||
* Dup archive init
|
||||
*
|
||||
* @param DupArchiveLoggerBase $logger logger object
|
||||
* @param string|null $targetRootPath archive target root path or null not root path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init(DupArchiveLoggerBase $logger, $targetRootPath = null)
|
||||
{
|
||||
DupArchiveUtil::$logger = $logger;
|
||||
self::$targetRootPath = $targetRootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local path
|
||||
*
|
||||
* @param string $path item path
|
||||
* @param DupArchiveCreateState $createState base path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getLocalPath($path, DupArchiveCreateState $createState)
|
||||
{
|
||||
$result = '';
|
||||
if (self::$targetRootPath === null) {
|
||||
$result = substr($path, $createState->basepathLength);
|
||||
$result = ltrim($result, '/');
|
||||
if ($createState->newBasePath !== null) {
|
||||
$result = $createState->newBasePath . $result;
|
||||
}
|
||||
} else {
|
||||
$safePath = SnapIO::safePathUntrailingslashit($path);
|
||||
$result = ltrim(
|
||||
$createState->newBasePath . preg_replace('/^' . preg_quote(self::$targetRootPath, '/') . '(.*)/m', '$1', $safePath),
|
||||
'/'
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get archvie info from path
|
||||
*
|
||||
* @param string $filepath archvie path
|
||||
*
|
||||
* @return DupArchiveInfo
|
||||
*/
|
||||
public static function getArchiveInfo($filepath)
|
||||
{
|
||||
$archiveInfo = new DupArchiveInfo();
|
||||
|
||||
DupArchiveUtil::log("archive size=" . filesize($filepath));
|
||||
$archiveHandle = SnapIO::fopen($filepath, 'rb');
|
||||
$archiveInfo->archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
||||
$moreToRead = true;
|
||||
|
||||
while ($moreToRead) {
|
||||
$headerType = self::getNextHeaderType($archiveHandle);
|
||||
|
||||
// DupArchiveUtil::log("next header type=$headerType: " . ftell($archiveHandle));
|
||||
|
||||
switch ($headerType) {
|
||||
case self::HEADER_TYPE_FILE:
|
||||
$fileHeader = DupArchiveFileHeader::readFromArchive($archiveHandle, true, true);
|
||||
$archiveInfo->fileHeaders[] = $fileHeader;
|
||||
DupArchiveUtil::log("file" . $fileHeader->relativePath);
|
||||
break;
|
||||
|
||||
case self::HEADER_TYPE_DIR:
|
||||
$directoryHeader = DupArchiveDirectoryHeader::readFromArchive($archiveHandle, true);
|
||||
|
||||
$archiveInfo->directoryHeaders[] = $directoryHeader;
|
||||
break;
|
||||
|
||||
case self::HEADER_TYPE_NONE:
|
||||
$moreToRead = false;
|
||||
}
|
||||
}
|
||||
return $archiveInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add folder to archive
|
||||
*
|
||||
* can't span requests since create state can't store list of files
|
||||
*
|
||||
* @param string $archiveFilepath archive file
|
||||
* @param string $directory folder to add
|
||||
* @param string $basepath base path to consider (?)
|
||||
* @param boolean $includeFiles if true include files
|
||||
* @param string $newBasepath new base path
|
||||
* @param int $globSize global size
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public static function addDirectoryToArchiveST(
|
||||
$archiveFilepath,
|
||||
$directory,
|
||||
$basepath,
|
||||
$includeFiles = false,
|
||||
$newBasepath = null,
|
||||
$globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE
|
||||
) {
|
||||
if ($includeFiles) {
|
||||
$scan = DupArchiveScanUtil::createScanObject($directory);
|
||||
} else {
|
||||
$scan = new stdClass();
|
||||
$scan->Files = array();
|
||||
$scan->Dirs = array();
|
||||
}
|
||||
|
||||
$createState = new DupArchiveSimpleCreateState();
|
||||
|
||||
$createState->archiveOffset = filesize($archiveFilepath);
|
||||
$createState->archivePath = $archiveFilepath;
|
||||
$createState->basePath = $basepath;
|
||||
$createState->basepathLength = strlen($basepath);
|
||||
$createState->timerEnabled = false;
|
||||
$createState->globSize = $globSize;
|
||||
$createState->newBasePath = $newBasepath;
|
||||
|
||||
self::addItemsToArchive($createState, $scan);
|
||||
|
||||
$retVal = new stdClass();
|
||||
$retVal->numDirsAdded = $createState->currentDirectoryIndex;
|
||||
$retVal->numFilesAdded = $createState->currentFileIndex;
|
||||
|
||||
if ($createState->skippedFileCount > 0) {
|
||||
throw new Exception("One or more files were were not able to be added when adding {$directory} to {$archiveFilepath}");
|
||||
} elseif ($createState->skippedDirectoryCount > 0) {
|
||||
throw new Exception("One or more directories were not able to be added when adding {$directory} to {$archiveFilepath}");
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add relative file to archive
|
||||
*
|
||||
* @param string $archiveFilepath archive file
|
||||
* @param string $filepath file to add
|
||||
* @param string $relativePath relative path in archive
|
||||
* @param int $globSize global size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addRelativeFileToArchiveST(
|
||||
$archiveFilepath,
|
||||
$filepath,
|
||||
$relativePath,
|
||||
$globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE
|
||||
) {
|
||||
$createState = new DupArchiveSimpleCreateState();
|
||||
|
||||
$createState->archiveOffset = filesize($archiveFilepath);
|
||||
$createState->archivePath = $archiveFilepath;
|
||||
$createState->basePath = null;
|
||||
$createState->basepathLength = 0;
|
||||
$createState->timerEnabled = false;
|
||||
$createState->globSize = $globSize;
|
||||
|
||||
$scan = new stdClass();
|
||||
|
||||
$scan->Files = array();
|
||||
$scan->Dirs = array();
|
||||
|
||||
$scan->Files[] = $filepath;
|
||||
|
||||
if ($relativePath != null) {
|
||||
$scan->FileAliases = array();
|
||||
$scan->FileAliases[$filepath] = $relativePath;
|
||||
}
|
||||
|
||||
self::addItemsToArchive($createState, $scan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file in archive from src
|
||||
*
|
||||
* @param string|resource $archive Archive path or archive handle
|
||||
* @param string $src source string
|
||||
* @param string $relativeFilePath relative path
|
||||
* @param int $forceSize if 0 size is auto of content is filled of \0 char to size
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function addFileFromSrc(
|
||||
$archive,
|
||||
$src,
|
||||
$relativeFilePath,
|
||||
$forceSize = 0
|
||||
) {
|
||||
if (is_resource($archive)) {
|
||||
$archiveHandle = $archive;
|
||||
SnapIO::fseek($archiveHandle, 0, SEEK_SET);
|
||||
} else {
|
||||
if (($archiveHandle = SnapIO::fopen($archive, 'r+b')) == false) {
|
||||
throw new Exception('Can\'t open archive');
|
||||
}
|
||||
}
|
||||
|
||||
$createState = new DupArchiveSimpleCreateState();
|
||||
$createState->archiveOffset = SnapIO::ftell($archiveHandle);
|
||||
$createState->basePath = dirname($relativeFilePath);
|
||||
$createState->basepathLength = strlen($createState->basePath);
|
||||
$createState->timerEnabled = false;
|
||||
|
||||
if ($forceSize == 0) {
|
||||
$archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
||||
$createState->isCompressed = $archiveHeader->isCompressed;
|
||||
} else {
|
||||
// ff force size is enables the src isn't compress
|
||||
$createState->isCompressed = false;
|
||||
}
|
||||
|
||||
SnapIO::fseek($archiveHandle, 0, SEEK_END);
|
||||
|
||||
$result = DupArchiveFileProcessor::writeFileSrcToArchive($createState, $archiveHandle, $src, $relativeFilePath, $forceSize);
|
||||
|
||||
if (!is_resource($archive)) {
|
||||
SnapIO::fclose($archiveHandle);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file in archive from src
|
||||
*
|
||||
* @param string $archiveFilepath archive path
|
||||
* @param string $src source string
|
||||
* @param string $relativeFilePath relative path
|
||||
* @param int $offset start search location
|
||||
* @param int $sizeToSearch max size where search
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function replaceFileContent(
|
||||
$archiveFilepath,
|
||||
$src,
|
||||
$relativeFilePath,
|
||||
$offset = 0,
|
||||
$sizeToSearch = 0
|
||||
) {
|
||||
if (($archiveHandle = SnapIO::fopen($archiveFilepath, 'r+b')) == false) {
|
||||
throw new Exception('Can\'t open archive');
|
||||
}
|
||||
|
||||
if (($filePos = self::searchPath($archiveHandle, $relativeFilePath, $offset, $sizeToSearch)) == false) {
|
||||
return false;
|
||||
}
|
||||
$fileHeader = DupArchiveReaderFileHeader::readFromArchive($archiveHandle);
|
||||
$globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle);
|
||||
SnapIO::fseek($archiveHandle, $filePos);
|
||||
|
||||
$createState = new DupArchiveSimpleCreateState();
|
||||
$createState->archivePath = $archiveFilepath;
|
||||
$createState->archiveOffset = $filePos;
|
||||
$createState->basePath = dirname($relativeFilePath);
|
||||
$createState->basepathLength = strlen($createState->basePath);
|
||||
$createState->timerEnabled = false;
|
||||
$createState->isCompressed = false; // replaced content can't be compressed
|
||||
|
||||
$forceSize = $globHeader->storedSize;
|
||||
|
||||
$result = DupArchiveFileProcessor::writeFileSrcToArchive($createState, $archiveHandle, $src, $relativeFilePath, $forceSize);
|
||||
SnapIO::fclose($archiveHandle);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add file in archive using base dir
|
||||
*
|
||||
* @param string $archiveFilepath archive file
|
||||
* @param string $basePath base path
|
||||
* @param string $filepath file to add
|
||||
* @param int $globSize global size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addFileToArchiveUsingBaseDirST(
|
||||
$archiveFilepath,
|
||||
$basePath,
|
||||
$filepath,
|
||||
$globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE
|
||||
) {
|
||||
$createState = new DupArchiveSimpleCreateState();
|
||||
|
||||
$createState->archiveOffset = filesize($archiveFilepath);
|
||||
$createState->archivePath = $archiveFilepath;
|
||||
$createState->basePath = $basePath;
|
||||
$createState->basepathLength = strlen($basePath);
|
||||
$createState->timerEnabled = false;
|
||||
$createState->globSize = $globSize;
|
||||
|
||||
$scan = new stdClass();
|
||||
|
||||
$scan->Files = array();
|
||||
$scan->Dirs = array();
|
||||
|
||||
$scan->Files[] = $filepath;
|
||||
|
||||
self::addItemsToArchive($createState, $scan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create archive
|
||||
*
|
||||
* @param string $archivePath archive file path
|
||||
* @param bool $isCompressed is compressed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function createArchive($archivePath, $isCompressed)
|
||||
{
|
||||
if (($archiveHandle = SnapIO::fopen($archivePath, 'w+b')) === false) {
|
||||
throw new Exception('Can\t create dup archvie file ' . $archivePath);
|
||||
}
|
||||
|
||||
$archiveHeader = DupArchiveHeader::create($isCompressed);
|
||||
$archiveHeader->writeToArchive($archiveHandle);
|
||||
|
||||
//reserver space for index
|
||||
$src = json_encode(array('test'));
|
||||
$src .= str_repeat("\0", self::INDEX_FILE_SIZE - strlen($src));
|
||||
self::addFileFromSrc($archiveHandle, $src, self::INDEX_FILE_NAME, self::INDEX_FILE_SIZE);
|
||||
|
||||
// Intentionally do not write build state since if something goes wrong we went it to start over on the archive
|
||||
SnapIO::fclose($archiveHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add items to archive
|
||||
*
|
||||
* @param DupArchiveCreateState $createState create state info
|
||||
* @param stdClass $scanFSInfo scan if
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addItemsToArchive(DupArchiveCreateState $createState, stdClass $scanFSInfo)
|
||||
{
|
||||
if ($createState->globSize == -1) {
|
||||
$createState->globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE;
|
||||
}
|
||||
|
||||
DupArchiveUtil::tlogObject("addItemsToArchive start", $createState);
|
||||
|
||||
$directoryCount = count($scanFSInfo->Dirs);
|
||||
$fileCount = count($scanFSInfo->Files);
|
||||
$createState->startTimer();
|
||||
$archiveHandle = SnapIO::fopen($createState->archivePath, 'r+b');
|
||||
|
||||
DupArchiveUtil::tlog("Archive size=", filesize($createState->archivePath));
|
||||
DupArchiveUtil::tlog("Archive location is now " . SnapIO::ftell($archiveHandle));
|
||||
|
||||
$archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
||||
|
||||
$createState->isCompressed = $archiveHeader->isCompressed;
|
||||
|
||||
if ($createState->archiveOffset == filesize($createState->archivePath)) {
|
||||
DupArchiveUtil::tlog(
|
||||
"Seeking to end of archive location because of offset {$createState->archiveOffset} " .
|
||||
"for file size " . filesize($createState->archivePath)
|
||||
);
|
||||
SnapIO::fseek($archiveHandle, 0, SEEK_END);
|
||||
} else {
|
||||
DupArchiveUtil::tlog("Seeking archive offset {$createState->archiveOffset} for file size " . filesize($createState->archivePath));
|
||||
SnapIO::fseek($archiveHandle, $createState->archiveOffset);
|
||||
}
|
||||
|
||||
while (($createState->currentDirectoryIndex < $directoryCount) && (!$createState->timedOut())) {
|
||||
if ($createState->throttleDelayInUs !== 0) {
|
||||
usleep($createState->throttleDelayInUs);
|
||||
}
|
||||
|
||||
$directory = $scanFSInfo->Dirs[$createState->currentDirectoryIndex];
|
||||
|
||||
try {
|
||||
$relativeDirectoryPath = '';
|
||||
|
||||
if (isset($scanFSInfo->DirectoryAliases) && array_key_exists($directory, $scanFSInfo->DirectoryAliases)) {
|
||||
$relativeDirectoryPath = $scanFSInfo->DirectoryAliases[$directory];
|
||||
} else {
|
||||
$relativeDirectoryPath = self::getLocalPath($directory, $createState);
|
||||
}
|
||||
|
||||
if ($relativeDirectoryPath !== '') {
|
||||
DupArchiveDirectoryProcessor::writeDirectoryToArchive($createState, $archiveHandle, $directory, $relativeDirectoryPath);
|
||||
} else {
|
||||
$createState->skippedDirectoryCount++;
|
||||
$createState->currentDirectoryIndex++;
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
DupArchiveUtil::log("Failed to add {$directory} to archive. Error: " . $ex->getMessage(), true);
|
||||
|
||||
$createState->addFailure(DupArchiveProcessingFailure::TYPE_DIRECTORY, $directory, $ex->getMessage(), false);
|
||||
$createState->currentDirectoryIndex++;
|
||||
$createState->skippedDirectoryCount++;
|
||||
$createState->save();
|
||||
}
|
||||
}
|
||||
|
||||
$createState->archiveOffset = SnapIO::ftell($archiveHandle);
|
||||
|
||||
$workTimestamp = time();
|
||||
while (($createState->currentFileIndex < $fileCount) && (!$createState->timedOut())) {
|
||||
$filepath = $scanFSInfo->Files[$createState->currentFileIndex];
|
||||
|
||||
try {
|
||||
$relativeFilePath = '';
|
||||
|
||||
if (isset($scanFSInfo->FileAliases) && array_key_exists($filepath, $scanFSInfo->FileAliases)) {
|
||||
$relativeFilePath = $scanFSInfo->FileAliases[$filepath];
|
||||
} else {
|
||||
$relativeFilePath = self::getLocalPath($filepath, $createState);
|
||||
}
|
||||
|
||||
// Uncomment when testing error handling
|
||||
// if((strpos($relativeFilePath, 'dup-installer') !== false) || (strpos($relativeFilePath, 'lib') !== false)) {
|
||||
// Dup_Log::Trace("Was going to do intentional error to {$relativeFilePath} but skipping");
|
||||
// } else {
|
||||
// throw new Exception("#### intentional file error when writing " . $relativeFilePath);
|
||||
// }
|
||||
// }
|
||||
|
||||
DupArchiveFileProcessor::writeFilePortionToArchive($createState, $archiveHandle, $filepath, $relativeFilePath);
|
||||
|
||||
if (($createState->isRobust) && (time() - $workTimestamp >= 1)) {
|
||||
DupArchiveUtil::log("Robust mode create state save");
|
||||
|
||||
// When in robustness mode save the state every second
|
||||
$workTimestamp = time();
|
||||
$createState->working = ($createState->currentDirectoryIndex < $directoryCount) || ($createState->currentFileIndex < $fileCount);
|
||||
$createState->save();
|
||||
}
|
||||
} catch (Snap32BitSizeLimitException $ex) {
|
||||
throw $ex;
|
||||
} catch (Exception $ex) {
|
||||
DupArchiveUtil::log("Failed to add {$filepath} to archive. Error: " . $ex->getMessage() . $ex->getTraceAsString(), true);
|
||||
$createState->currentFileIndex++;
|
||||
$createState->skippedFileCount++;
|
||||
$createState->addFailure(DupArchiveProcessingFailure::TYPE_FILE, $filepath, $ex->getMessage(), ($ex->getCode() === self::EXCEPTION_FATAL));
|
||||
$createState->save();
|
||||
}
|
||||
}
|
||||
|
||||
$createState->working = ($createState->currentDirectoryIndex < $directoryCount) || ($createState->currentFileIndex < $fileCount);
|
||||
$createState->save();
|
||||
|
||||
SnapIO::fclose($archiveHandle);
|
||||
|
||||
if (!$createState->working) {
|
||||
DupArchiveUtil::log("compress done");
|
||||
} else {
|
||||
DupArchiveUtil::tlog("compress not done so continuing later");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand archive
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState expand state
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function expandArchive(DupArchiveExpandState $expandState)
|
||||
{
|
||||
$expandState->startTimer();
|
||||
$archiveHandle = SnapIO::fopen($expandState->archivePath, 'rb');
|
||||
|
||||
SnapIO::fseek($archiveHandle, $expandState->archiveOffset);
|
||||
|
||||
if ($expandState->archiveOffset == 0) {
|
||||
$expandState->archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
||||
$expandState->isCompressed = $expandState->archiveHeader->isCompressed;
|
||||
$expandState->archiveOffset = SnapIO::ftell($archiveHandle);
|
||||
|
||||
$expandState->save();
|
||||
} else {
|
||||
DupArchiveUtil::log("#### seeking archive offset {$expandState->archiveOffset}");
|
||||
}
|
||||
|
||||
DupArchiveUtil::log('DUP EXPAND OFFSET ' . $expandState->archiveOffset);
|
||||
|
||||
if ((!$expandState->validateOnly) || ($expandState->validationType == DupArchiveExpandState::VALIDATION_FULL)) {
|
||||
$moreItems = self::expandItems($expandState, $archiveHandle);
|
||||
} else {
|
||||
$moreItems = self::standardValidateItems($expandState, $archiveHandle);
|
||||
}
|
||||
|
||||
$expandState->working = $moreItems;
|
||||
$expandState->save();
|
||||
|
||||
SnapIO::fclose($archiveHandle, false);
|
||||
|
||||
if (!$expandState->working) {
|
||||
DupArchiveUtil::log("DUP EXPAND DONE");
|
||||
|
||||
if (($expandState->expectedFileCount != -1) && ($expandState->expectedFileCount != $expandState->fileWriteCount)) {
|
||||
$expandState->addFailure(
|
||||
DupArchiveProcessingFailure::TYPE_FILE,
|
||||
'Archive',
|
||||
"Number of files expected ({$expandState->expectedFileCount}) doesn't equal number written ({$expandState->fileWriteCount})."
|
||||
);
|
||||
}
|
||||
|
||||
if (($expandState->expectedDirectoryCount != -1) && ($expandState->expectedDirectoryCount != $expandState->directoryWriteCount)) {
|
||||
$expandState->addFailure(
|
||||
DupArchiveProcessingFailure::TYPE_DIRECTORY,
|
||||
'Archive',
|
||||
"Number of directories expected ({$expandState->expectedDirectoryCount}) " .
|
||||
"doesn't equal number written ({$expandState->directoryWriteCount})."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
DupArchiveUtil::tlogObject("expand not done so continuing later", $expandState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single-threaded file expansion
|
||||
*
|
||||
* @param string $archiveFilePath archive path
|
||||
* @param string $relativeFilePaths relative file path in archive
|
||||
* @param string $destPath destination path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function expandFiles($archiveFilePath, $relativeFilePaths, $destPath)
|
||||
{
|
||||
// Not setting timeout timestamp so it will never timeout
|
||||
DupArchiveUtil::tlog("opening archive {$archiveFilePath}");
|
||||
|
||||
$archiveHandle = SnapIO::fopen($archiveFilePath, 'r');
|
||||
|
||||
/* @var $expandState DupArchiveSimpleExpandState */
|
||||
$expandState = new DupArchiveSimpleExpandState();
|
||||
|
||||
$expandState->archiveHeader = DupArchiveHeader::readFromArchive($archiveHandle);
|
||||
$expandState->isCompressed = $expandState->archiveHeader->isCompressed;
|
||||
$expandState->archiveOffset = SnapIO::ftell($archiveHandle);
|
||||
$expandState->includedFiles = $relativeFilePaths;
|
||||
$expandState->filteredDirectories = array('*');
|
||||
$expandState->filteredFiles = array('*');
|
||||
// $expandState->basePath = $destPath . '/tempExtract'; // RSR remove once extract works
|
||||
$expandState->basePath = $destPath; // RSR remove once extract works
|
||||
// TODO: Filter out all directories/files except those in the list
|
||||
self::expandItems($expandState, $archiveHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand dup archive items
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState dup archive expand state
|
||||
* @param resource $archiveHandle dup archvie resource
|
||||
*
|
||||
* @return bool true if more to read
|
||||
*/
|
||||
private static function expandItems(DupArchiveExpandState $expandState, $archiveHandle)
|
||||
{
|
||||
$moreToRead = true;
|
||||
$workTimestamp = time();
|
||||
|
||||
while ($moreToRead && (!$expandState->timedOut())) {
|
||||
if ($expandState->throttleDelayInUs !== 0) {
|
||||
usleep($expandState->throttleDelayInUs);
|
||||
}
|
||||
|
||||
if ($expandState->currentFileHeader != null) {
|
||||
DupArchiveUtil::tlog("Writing file {$expandState->currentFileHeader->relativePath}");
|
||||
|
||||
if (self::filePassesFilters($expandState)) {
|
||||
try {
|
||||
$fileCompleted = DupArchiveFileProcessor::writeToFile($expandState, $archiveHandle);
|
||||
} catch (Exception $ex) {
|
||||
DupArchiveUtil::log("Failed to write to {$expandState->currentFileHeader->relativePath}. Error: " . $ex->getMessage(), true);
|
||||
|
||||
// Reset things - skip over this file within the archive.
|
||||
SnapIO::fseek($archiveHandle, $expandState->lastHeaderOffset);
|
||||
self::skipToNextHeader($archiveHandle, $expandState->currentFileHeader);
|
||||
|
||||
$expandState->archiveOffset = ftell($archiveHandle);
|
||||
$expandState->addFailure(
|
||||
DupArchiveProcessingFailure::TYPE_FILE,
|
||||
$expandState->currentFileHeader->relativePath,
|
||||
$ex->getMessage(),
|
||||
false
|
||||
);
|
||||
$expandState->resetForFile();
|
||||
$expandState->lastHeaderOffset = -1;
|
||||
$expandState->save();
|
||||
}
|
||||
} else {
|
||||
self::skipFileInArchive($archiveHandle, $expandState->currentFileHeader);
|
||||
$expandState->resetForFile();
|
||||
}
|
||||
} else {
|
||||
// Header is null so read in the next one
|
||||
$expandState->lastHeaderOffset = @ftell($archiveHandle);
|
||||
$headerType = self::getNextHeaderType($archiveHandle);
|
||||
|
||||
DupArchiveUtil::tlog('header type ' . $headerType);
|
||||
switch ($headerType) {
|
||||
case self::HEADER_TYPE_FILE:
|
||||
DupArchiveUtil::tlog('File header');
|
||||
$expandState->currentFileHeader = DupArchiveFileHeader::readFromArchive($archiveHandle, false, true);
|
||||
$expandState->archiveOffset = @ftell($archiveHandle);
|
||||
DupArchiveUtil::tlog('Just read file header from archive');
|
||||
break;
|
||||
case self::HEADER_TYPE_DIR:
|
||||
DupArchiveUtil::tlog('Directory Header');
|
||||
$directoryHeader = DupArchiveDirectoryHeader::readFromArchive($archiveHandle, true);
|
||||
|
||||
if (self::passesDirectoryExclusion($expandState, $directoryHeader->relativePath)) {
|
||||
$createdDirectory = true;
|
||||
|
||||
if (!$expandState->validateOnly) {
|
||||
$createdDirectory = DupArchiveFileProcessor::createDirectory($expandState, $directoryHeader);
|
||||
}
|
||||
|
||||
if ($createdDirectory) {
|
||||
$expandState->directoryWriteCount++;
|
||||
}
|
||||
}
|
||||
$expandState->archiveOffset = ftell($archiveHandle);
|
||||
DupArchiveUtil::tlog('Just read directory header ' . $directoryHeader->relativePath . ' from archive');
|
||||
break;
|
||||
case self::HEADER_TYPE_NONE:
|
||||
$moreToRead = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (($expandState->isRobust) && (time() - $workTimestamp >= 1)) {
|
||||
DupArchiveUtil::log("Robust mode extract state save for standard validate");
|
||||
|
||||
// When in robustness mode save the state every second
|
||||
$workTimestamp = time();
|
||||
$expandState->save();
|
||||
}
|
||||
}
|
||||
|
||||
$expandState->save();
|
||||
|
||||
return $moreToRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* check exclude dir
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState dup archive expand state
|
||||
* @param string $candidate check exclude dir
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function passesDirectoryExclusion(DupArchiveExpandState $expandState, $candidate)
|
||||
{
|
||||
foreach ($expandState->filteredDirectories as $directoryFilter) {
|
||||
if ($directoryFilter === '*') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SnapIO::getRelativePath($candidate, $directoryFilter) !== false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($candidate, $expandState->excludedDirWithoutChilds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check flils filters
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState dup archive expand state
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private static function filePassesFilters(DupArchiveExpandState $expandState)
|
||||
{
|
||||
$candidate = $expandState->currentFileHeader->relativePath;
|
||||
|
||||
// Included files trumps all exclusion filters
|
||||
foreach ($expandState->includedFiles as $includedFile) {
|
||||
if ($includedFile === $candidate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::passesDirectoryExclusion($expandState, $candidate)) {
|
||||
foreach ($expandState->filteredFiles as $fileFilter) {
|
||||
if ($fileFilter === '*' || $fileFilter === $candidate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate items
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState dup archive expan state
|
||||
* @param resource $archiveHandle dup archive resource
|
||||
*
|
||||
* @return bool true if more to read
|
||||
*/
|
||||
private static function standardValidateItems(DupArchiveExpandState $expandState, $archiveHandle)
|
||||
{
|
||||
$moreToRead = true;
|
||||
|
||||
$to = $expandState->timedOut();
|
||||
$workTimestamp = time();
|
||||
|
||||
while ($moreToRead && (!$to)) {
|
||||
if ($expandState->throttleDelayInUs !== 0) {
|
||||
usleep($expandState->throttleDelayInUs);
|
||||
}
|
||||
|
||||
if ($expandState->currentFileHeader != null) {
|
||||
try {
|
||||
$fileCompleted = DupArchiveFileProcessor::standardValidateFileEntry($expandState, $archiveHandle);
|
||||
|
||||
if ($fileCompleted) {
|
||||
$expandState->resetForFile();
|
||||
}
|
||||
|
||||
// Expand state taken care of within the write to file to ensure consistency
|
||||
} catch (Exception $ex) {
|
||||
DupArchiveUtil::log("Failed validate file in archive. Error: " . $ex->getMessage(), true);
|
||||
DupArchiveUtil::logObject("expand state", $expandState, true);
|
||||
// $expandState->currentFileIndex++;
|
||||
// RSR TODO: Need way to skip past that file
|
||||
|
||||
$expandState->addFailure(DupArchiveProcessingFailure::TYPE_FILE, $expandState->currentFileHeader->relativePath, $ex->getMessage());
|
||||
$expandState->save();
|
||||
|
||||
$moreToRead = false;
|
||||
}
|
||||
} else {
|
||||
$headerType = self::getNextHeaderType($archiveHandle);
|
||||
|
||||
switch ($headerType) {
|
||||
case self::HEADER_TYPE_FILE:
|
||||
$expandState->currentFileHeader = DupArchiveFileHeader::readFromArchive($archiveHandle, false, true);
|
||||
$expandState->archiveOffset = ftell($archiveHandle);
|
||||
break;
|
||||
case self::HEADER_TYPE_DIR:
|
||||
$directoryHeader = DupArchiveDirectoryHeader::readFromArchive($archiveHandle, true);
|
||||
$expandState->directoryWriteCount++;
|
||||
$expandState->archiveOffset = ftell($archiveHandle);
|
||||
break;
|
||||
case self::HEADER_TYPE_NONE:
|
||||
$moreToRead = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (($expandState->isRobust) && (time() - $workTimestamp >= 1)) {
|
||||
DupArchiveUtil::log("Robust mdoe extract state save for standard validate");
|
||||
|
||||
// When in robustness mode save the state every second
|
||||
$workTimestamp = time();
|
||||
$expandState->save();
|
||||
}
|
||||
$to = $expandState->timedOut();
|
||||
}
|
||||
|
||||
$expandState->save();
|
||||
|
||||
return $moreToRead;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive;
|
||||
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderDirectoryHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderFileHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderGlobHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveReaderHeader;
|
||||
use Duplicator\Libs\DupArchive\Info\DupArchiveExpanderInfo;
|
||||
use Exception;
|
||||
|
||||
class DupArchiveExpandBasicEngine extends DupArchive
|
||||
{
|
||||
protected static $logCallback = null;
|
||||
protected static $chmodCallback = null;
|
||||
protected static $mkdirCallback = null;
|
||||
|
||||
/**
|
||||
* Set callabcks function
|
||||
*
|
||||
* @param null|callable $log function callback
|
||||
* @param null|callable $chmod function callback
|
||||
* @param null|callable $mkdir function callback
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setCallbacks($log, $chmod, $mkdir)
|
||||
{
|
||||
self::$logCallback = (is_callable($log) ? $log : null);
|
||||
self::$chmodCallback = (is_callable($chmod) ? $chmod : null);
|
||||
self::$mkdirCallback = (is_callable($mkdir) ? $mkdir : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write log
|
||||
*
|
||||
* @param string $s string
|
||||
* @param bool $flush if true flush file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function log($s, $flush = false)
|
||||
{
|
||||
if (self::$logCallback == null) {
|
||||
return;
|
||||
}
|
||||
call_user_func(self::$logCallback, "MINI EXPAND:$s", $flush);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand folder
|
||||
*
|
||||
* @param string $archivePath archive path
|
||||
* @param string $relativePath relative path
|
||||
* @param string $destPath dest path
|
||||
* @param bool $ignoreErrors if true ignore errors
|
||||
* @param int $offset start scan location
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function expandDirectory($archivePath, $relativePath, $destPath, $ignoreErrors = false, $offset = 0)
|
||||
{
|
||||
self::expandItems($archivePath, $relativePath, $destPath, $ignoreErrors, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand items
|
||||
*
|
||||
* @param string $archivePath archive path
|
||||
* @param string[] $inclusionFilter filters
|
||||
* @param string $destDirectory dest path
|
||||
* @param bool $ignoreErrors if true ignore errors
|
||||
* @param int $offset start scan location
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function expandItems($archivePath, $inclusionFilter, $destDirectory, $ignoreErrors = false, $offset = 0)
|
||||
{
|
||||
$archiveHandle = fopen($archivePath, 'rb');
|
||||
|
||||
if ($archiveHandle === false) {
|
||||
throw new Exception("Can’t open archive at $archivePath!");
|
||||
}
|
||||
|
||||
$archiveHeader = DupArchiveReaderHeader::readFromArchive($archiveHandle);
|
||||
$writeInfo = new DupArchiveExpanderInfo();
|
||||
|
||||
$writeInfo->destDirectory = $destDirectory;
|
||||
$writeInfo->isCompressed = $archiveHeader->isCompressed;
|
||||
|
||||
if ($offset > 0) {
|
||||
fseek($archiveHandle, $offset);
|
||||
}
|
||||
|
||||
$moreToRead = true;
|
||||
|
||||
while ($moreToRead) {
|
||||
if ($writeInfo->currentFileHeader != null) {
|
||||
try {
|
||||
if (self::passesInclusionFilter($inclusionFilter, $writeInfo->currentFileHeader->relativePath)) {
|
||||
self::writeToFile($archiveHandle, $writeInfo);
|
||||
$writeInfo->fileWriteCount++;
|
||||
} elseif ($writeInfo->currentFileHeader->fileSize > 0) {
|
||||
self::skipFileInArchive($archiveHandle, $writeInfo->currentFileHeader);
|
||||
}
|
||||
$writeInfo->currentFileHeader = null;
|
||||
// Expand state taken care of within the write to file to ensure consistency
|
||||
} catch (Exception $ex) {
|
||||
if (!$ignoreErrors) {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$headerType = self::getNextHeaderType($archiveHandle);
|
||||
|
||||
switch ($headerType) {
|
||||
case self::HEADER_TYPE_FILE:
|
||||
$writeInfo->currentFileHeader = DupArchiveReaderFileHeader::readFromArchive($archiveHandle, false, true);
|
||||
break;
|
||||
case self::HEADER_TYPE_DIR:
|
||||
$directoryHeader = DupArchiveReaderDirectoryHeader::readFromArchive($archiveHandle, true);
|
||||
// self::log("considering $inclusionFilter and {$directoryHeader->relativePath}");
|
||||
if (self::passesInclusionFilter($inclusionFilter, $directoryHeader->relativePath)) {
|
||||
// self::log("passed");
|
||||
$directory = "{$writeInfo->destDirectory}/{$directoryHeader->relativePath}";
|
||||
|
||||
// $mode = $directoryHeader->permissions;
|
||||
// rodo handle this more elegantly @mkdir($directory, $directoryHeader->permissions, true);
|
||||
if (is_callable(self::$mkdirCallback)) {
|
||||
call_user_func(self::$mkdirCallback, $directory, 'u+rwx', true);
|
||||
} else {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
$writeInfo->directoryWriteCount++;
|
||||
} else {
|
||||
// self::log("didnt pass");
|
||||
}
|
||||
break;
|
||||
case self::HEADER_TYPE_NONE:
|
||||
$moreToRead = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose($archiveHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to file
|
||||
*
|
||||
* @param resource $archiveHandle archive file handle
|
||||
* @param DupArchiveExpanderInfo $writeInfo write info
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function writeToFile($archiveHandle, DupArchiveExpanderInfo $writeInfo)
|
||||
{
|
||||
$destFilePath = $writeInfo->getCurrentDestFilePath();
|
||||
|
||||
if ($writeInfo->currentFileHeader->fileSize > 0) {
|
||||
$parentDir = dirname($destFilePath);
|
||||
if (!file_exists($parentDir)) {
|
||||
if (is_callable(self::$mkdirCallback)) {
|
||||
$res = call_user_func(self::$mkdirCallback, $parentDir, 'u+rwx', true);
|
||||
} else {
|
||||
$res = mkdir($parentDir, 0755, true);
|
||||
}
|
||||
if (!$res) {
|
||||
throw new Exception("Couldn't create {$parentDir}");
|
||||
}
|
||||
}
|
||||
|
||||
$destFileHandle = fopen($destFilePath, 'wb+');
|
||||
if ($destFileHandle === false) {
|
||||
throw new Exception("Couldn't open {$destFilePath} for writing.");
|
||||
}
|
||||
|
||||
do {
|
||||
self::appendGlobToFile($archiveHandle, $destFileHandle, $writeInfo);
|
||||
|
||||
$currentFileOffset = ftell($destFileHandle);
|
||||
|
||||
$moreGlobstoProcess = $currentFileOffset < $writeInfo->currentFileHeader->fileSize;
|
||||
} while ($moreGlobstoProcess);
|
||||
|
||||
fclose($destFileHandle);
|
||||
|
||||
if (is_callable(self::$chmodCallback)) {
|
||||
call_user_func(self::$chmodCallback, $destFilePath, 'u+rw');
|
||||
} else {
|
||||
chmod($destFilePath, 0644);
|
||||
}
|
||||
|
||||
self::validateExpandedFile($writeInfo);
|
||||
} else {
|
||||
if (touch($destFilePath) === false) {
|
||||
throw new Exception("Couldn't create $destFilePath");
|
||||
}
|
||||
|
||||
if (is_callable(self::$chmodCallback)) {
|
||||
call_user_func(self::$chmodCallback, $destFilePath, 'u+rw');
|
||||
} else {
|
||||
chmod($destFilePath, 0644);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file
|
||||
*
|
||||
* @param DupArchiveExpanderInfo $writeInfo write info
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function validateExpandedFile(DupArchiveExpanderInfo $writeInfo)
|
||||
{
|
||||
if ($writeInfo->currentFileHeader->hash !== '00000000000000000000000000000000') {
|
||||
$hash = hash_file('crc32b', $writeInfo->getCurrentDestFilePath());
|
||||
|
||||
if ($hash !== $writeInfo->currentFileHeader->hash) {
|
||||
throw new Exception("MD5 validation fails for {$writeInfo->getCurrentDestFilePath()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* Assumption is that archive handle points to a glob header on this call
|
||||
*
|
||||
* @param resource $archiveHandle archive handle
|
||||
* @param resource $destFileHandle dest file handle
|
||||
* @param DupArchiveExpanderInfo $writeInfo write info
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function appendGlobToFile($archiveHandle, $destFileHandle, DupArchiveExpanderInfo $writeInfo)
|
||||
{
|
||||
$globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle, false);
|
||||
$globContents = fread($archiveHandle, $globHeader->storedSize);
|
||||
|
||||
if ($globContents === false) {
|
||||
throw new Exception("Error reading glob from " . $writeInfo->getCurrentDestFilePath());
|
||||
}
|
||||
|
||||
if ($writeInfo->isCompressed) {
|
||||
$globContents = gzinflate($globContents);
|
||||
}
|
||||
|
||||
if (fwrite($destFileHandle, $globContents) !== strlen($globContents)) {
|
||||
throw new Exception("Unable to write all bytes of data glob to storage.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check filter
|
||||
*
|
||||
* @param string $filter filter
|
||||
* @param string $candidate candidate
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function passesInclusionFilter($filter, $candidate)
|
||||
{
|
||||
return (substr($candidate, 0, strlen($filter)) == $filter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive;
|
||||
|
||||
abstract class DupArchiveLoggerBase
|
||||
{
|
||||
/**
|
||||
* Log function
|
||||
*
|
||||
* @param string $s string to log
|
||||
* @param boolean $flush if true flish log
|
||||
* @param callback|null $callingFunctionOverride call back function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function log($s, $flush = false, $callingFunctionOverride = null);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Headers;
|
||||
|
||||
use Exception;
|
||||
|
||||
// Format
|
||||
class DupArchiveDirectoryHeader extends DupArchiveReaderDirectoryHeader
|
||||
{
|
||||
public $mtime = 0;
|
||||
public $permissions = '';
|
||||
public $relativePathLength = 1;
|
||||
public $relativePath = '';
|
||||
|
||||
/**
|
||||
* Write header in archive
|
||||
*
|
||||
* @param resource $archiveHandle archive resource
|
||||
*
|
||||
* @return int bytes written
|
||||
*/
|
||||
public function writeToArchive($archiveHandle)
|
||||
{
|
||||
if ($this->relativePathLength == 0) {
|
||||
// Don't allow a base path to be written to the archive
|
||||
return;
|
||||
}
|
||||
|
||||
$headerString = '<D><MT>' .
|
||||
$this->mtime . '</MT><P>' .
|
||||
$this->permissions . '</P><RPL>' .
|
||||
$this->relativePathLength . '</RPL><RP>' .
|
||||
$this->relativePath . '</RP></D>';
|
||||
|
||||
//SnapIO::fwrite($archiveHandle, $headerString);
|
||||
$bytes_written = @fwrite($archiveHandle, $headerString);
|
||||
|
||||
if ($bytes_written === false) {
|
||||
throw new Exception('Error writing to file.');
|
||||
} else {
|
||||
return $bytes_written;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Headers;
|
||||
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* File header
|
||||
*/
|
||||
class DupArchiveFileHeader extends DupArchiveReaderFileHeader
|
||||
{
|
||||
const MAX_SIZE_FOR_HASHING = 1000000000;
|
||||
|
||||
public $fileSize = 0;
|
||||
public $mtime = 0;
|
||||
public $permissions = '';
|
||||
public $hash = '';
|
||||
public $relativePathLength = 0;
|
||||
public $relativePath = '';
|
||||
|
||||
/**
|
||||
* create header from file
|
||||
*
|
||||
* @param string $filepath file path
|
||||
* @param string $relativeFilePath relative file path in archive
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function createFromFile($filepath, $relativeFilePath)
|
||||
{
|
||||
$instance = new static();
|
||||
|
||||
|
||||
$instance->fileSize = SnapIO::filesize($filepath);
|
||||
$instance->permissions = substr(sprintf('%o', fileperms($filepath)), -4);
|
||||
$instance->mtime = SnapIO::filemtime($filepath);
|
||||
|
||||
if ($instance->fileSize > self::MAX_SIZE_FOR_HASHING) {
|
||||
$instance->hash = "00000000000000000000000000000000";
|
||||
} else {
|
||||
$instance->hash = hash_file('crc32b', $filepath);
|
||||
}
|
||||
|
||||
$instance->relativePath = $relativeFilePath;
|
||||
$instance->relativePathLength = strlen($instance->relativePath);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* create header from src
|
||||
*
|
||||
* @param string $src source string
|
||||
* @param string $relativeFilePath relative path in archvie
|
||||
* @param int $forceSize if 0 size is auto of content is filled of \0 char to size
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function createFromSrc($src, $relativeFilePath, $forceSize = 0)
|
||||
{
|
||||
$instance = new static();
|
||||
|
||||
$instance->fileSize = strlen($src);
|
||||
$instance->permissions = '0644';
|
||||
$instance->mtime = time();
|
||||
|
||||
$srcLen = strlen($src);
|
||||
|
||||
if ($forceSize > 0 && $srcLen < $forceSize) {
|
||||
$charsToAdd = $forceSize - $srcLen;
|
||||
$src .= str_repeat("\0", $charsToAdd);
|
||||
}
|
||||
|
||||
if ($instance->fileSize > self::MAX_SIZE_FOR_HASHING) {
|
||||
$instance->hash = "00000000000000000000000000000000";
|
||||
} else {
|
||||
$instance->hash = hash('crc32b', $src);
|
||||
}
|
||||
|
||||
$instance->relativePath = $relativeFilePath;
|
||||
$instance->relativePathLength = strlen($instance->relativePath);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write header in archive
|
||||
*
|
||||
* @param resource $archiveHandle archive resource
|
||||
*
|
||||
* @return int bytes written
|
||||
*/
|
||||
public function writeToArchive($archiveHandle)
|
||||
{
|
||||
$headerString = '<F><FS>' .
|
||||
$this->fileSize . '</FS><MT>' .
|
||||
$this->mtime . '</MT><P>' .
|
||||
$this->permissions . '</P><HA>' .
|
||||
$this->hash . '</HA><RPL>' .
|
||||
$this->relativePathLength . '</RPL><RP>' .
|
||||
$this->relativePath . '</RP></F>';
|
||||
|
||||
//SnapIO::fwrite($archiveHandle, $headerString);
|
||||
$bytes_written = @fwrite($archiveHandle, $headerString);
|
||||
|
||||
if ($bytes_written === false) {
|
||||
throw new Exception('Error writing to file.');
|
||||
} else {
|
||||
return $bytes_written;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Headers;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Dup archive glob header
|
||||
*
|
||||
* Format
|
||||
* #C#{$originalSize}#{$storedSize}!
|
||||
*/
|
||||
class DupArchiveGlobHeader extends DupArchiveReaderGlobHeader
|
||||
{
|
||||
// public $marker;
|
||||
public $originalSize;
|
||||
public $storedSize;
|
||||
public $hash;
|
||||
|
||||
/**
|
||||
* Write header in archive
|
||||
*
|
||||
* @param resource $archiveHandle archive file resource
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function writeToArchive($archiveHandle)
|
||||
{
|
||||
// <G><OS>x</OS>x<SS>x</SS><HA>x</HA></G>
|
||||
|
||||
$headerString = '<G><OS>' . $this->originalSize . '</OS><SS>' . $this->storedSize . '</SS><HA>' . $this->hash . '</HA></G>';
|
||||
|
||||
//SnapIO::fwrite($archiveHandle, $headerString);
|
||||
$bytes_written = @fwrite($archiveHandle, $headerString);
|
||||
|
||||
if ($bytes_written === false) {
|
||||
throw new Exception('Error writing to file.');
|
||||
} else {
|
||||
return $bytes_written;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Headers;
|
||||
|
||||
use Duplicator\Libs\DupArchive\DupArchiveEngine;
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Dup archive header
|
||||
*
|
||||
* Format: #A#{version:5}#{isCompressed}!
|
||||
*/
|
||||
class DupArchiveHeader extends DupArchiveReaderHeader
|
||||
{
|
||||
/** @var string */
|
||||
protected $version;
|
||||
/** @var bool */
|
||||
public $isCompressed;
|
||||
|
||||
/**
|
||||
* Create new header
|
||||
*
|
||||
* @param bool $isCompressed true if is compressed
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function create($isCompressed)
|
||||
{
|
||||
$instance = new self();
|
||||
$instance->version = DupArchiveEngine::DUPARCHIVE_VERSION;
|
||||
$instance->isCompressed = $isCompressed;
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write header to archive
|
||||
*
|
||||
* @param resource $archiveHandle archive resource
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function writeToArchive($archiveHandle)
|
||||
{
|
||||
SnapIO::fwrite($archiveHandle, '<A><V>' . $this->version . '</V><C>' . ($this->isCompressed ? 'true' : 'false') . '</C></A>');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Headers;
|
||||
|
||||
use Exception;
|
||||
|
||||
class DupArchiveHeaderU
|
||||
{
|
||||
const MAX_FILED_LEN = 128;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param resource $archiveHandle archvie resource
|
||||
* @param int $ename header enum
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function readStandardHeaderField($archiveHandle, $ename)
|
||||
{
|
||||
$expectedStart = '<' . $ename . '>';
|
||||
$expectedEnd = '</' . $ename . '>';
|
||||
|
||||
$startingElement = fread($archiveHandle, strlen($expectedStart));
|
||||
|
||||
if ($startingElement !== $expectedStart) {
|
||||
throw new Exception("Invalid starting element. Was expecting {$expectedStart} but got {$startingElement}");
|
||||
}
|
||||
|
||||
$headerString = stream_get_line($archiveHandle, self::MAX_FILED_LEN, $expectedEnd);
|
||||
|
||||
if ($headerString === false) {
|
||||
throw new Exception('Error reading line.');
|
||||
}
|
||||
|
||||
return $headerString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Headers;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class dir header reader
|
||||
*/
|
||||
class DupArchiveReaderDirectoryHeader
|
||||
{
|
||||
public $mtime = 0;
|
||||
public $permissions = '';
|
||||
public $relativePathLength = 1;
|
||||
public $relativePath = '';
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Read folder from archive
|
||||
*
|
||||
* @param resource $archiveHandle archive resource
|
||||
* @param boolean $skipStartElement if true sckip start element
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function readFromArchive($archiveHandle, $skipStartElement = false)
|
||||
{
|
||||
$instance = new static();
|
||||
|
||||
if (!$skipStartElement) {
|
||||
// <A>
|
||||
$startElement = fread($archiveHandle, 3);
|
||||
|
||||
if ($startElement === false) {
|
||||
if (feof($archiveHandle)) {
|
||||
return false;
|
||||
} else {
|
||||
throw new Exception('Error reading directory header');
|
||||
}
|
||||
}
|
||||
|
||||
if ($startElement != '<D>') {
|
||||
throw new Exception("Invalid directory header marker found [{$startElement}] : location " . ftell($archiveHandle));
|
||||
}
|
||||
}
|
||||
|
||||
$instance->mtime = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'MT');
|
||||
$instance->permissions = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'P');
|
||||
$instance->relativePathLength = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'RPL');
|
||||
|
||||
// Skip the <RP>
|
||||
fread($archiveHandle, 4);
|
||||
|
||||
$instance->relativePath = fread($archiveHandle, $instance->relativePathLength);
|
||||
|
||||
// Skip the </RP>
|
||||
// fread($archiveHandle, 5);
|
||||
|
||||
// Skip the </D>
|
||||
// fread($archiveHandle, 4);
|
||||
|
||||
// Skip the </RP> and the </D>
|
||||
fread($archiveHandle, 9);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Headers;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* File header
|
||||
*/
|
||||
class DupArchiveReaderFileHeader
|
||||
{
|
||||
public $fileSize = 0;
|
||||
public $mtime = 0;
|
||||
public $permissions = '';
|
||||
public $hash = '';
|
||||
public $relativePathLength = 0;
|
||||
public $relativePath = '';
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
// Prevent direct instantiation
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read header form archive
|
||||
* delta = 84-22 = 62 bytes per file -> 20000 files -> 1.2MB larger
|
||||
* <F><FS>x</FS><MT>x</<MT><FP>x</FP><HA>x</HA><RFPL>x</RFPL><RFP>x</RFP></F>
|
||||
* # F#x#x#x#x#x#x!
|
||||
*
|
||||
* @param resource $archiveHandle archive resource
|
||||
* @param boolean $skipContents if true skip contents
|
||||
* @param boolean $skipMarker if true skip marker
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function readFromArchive($archiveHandle, $skipContents = false, $skipMarker = false)
|
||||
{
|
||||
// RSR TODO Read header from archive handle and populate members
|
||||
// TODO: return null if end of archive or throw exception if can read something but its not a file header
|
||||
|
||||
$instance = new static();
|
||||
|
||||
if (!$skipMarker) {
|
||||
$marker = @fread($archiveHandle, 3);
|
||||
|
||||
if ($marker === false) {
|
||||
if (feof($archiveHandle)) {
|
||||
return false;
|
||||
} else {
|
||||
throw new Exception('Error reading file header');
|
||||
}
|
||||
}
|
||||
|
||||
if ($marker != '<F>') {
|
||||
throw new Exception("Invalid file header marker found [{$marker}] : location " . ftell($archiveHandle));
|
||||
}
|
||||
}
|
||||
|
||||
$instance->fileSize = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'FS');
|
||||
$instance->mtime = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'MT');
|
||||
$instance->permissions = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'P');
|
||||
$instance->hash = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'HA');
|
||||
$instance->relativePathLength = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'RPL');
|
||||
|
||||
// Skip <RP>
|
||||
fread($archiveHandle, 4);
|
||||
$instance->relativePath = fread($archiveHandle, $instance->relativePathLength);
|
||||
|
||||
// Skip </RP>
|
||||
// fread($archiveHandle, 5);
|
||||
|
||||
// Skip the </F>
|
||||
// fread($archiveHandle, 4);
|
||||
|
||||
// Skip the </RP> and the </F>
|
||||
fread($archiveHandle, 9);
|
||||
|
||||
if ($skipContents && ($instance->fileSize > 0)) {
|
||||
$dataSize = 0;
|
||||
$moreGlobs = true;
|
||||
while ($moreGlobs) {
|
||||
$globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle, true);
|
||||
$dataSize += $globHeader->originalSize;
|
||||
$moreGlobs = ($dataSize < $instance->fileSize);
|
||||
}
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Headers;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Dup archive glob header
|
||||
*
|
||||
* Format
|
||||
* #C#{$originalSize}#{$storedSize}!
|
||||
*/
|
||||
class DupArchiveReaderGlobHeader
|
||||
{
|
||||
// public $marker;
|
||||
public $originalSize;
|
||||
public $storedSize;
|
||||
public $hash;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Read chunk file header from archive
|
||||
*
|
||||
* @param resource $archiveHandle archive file resource
|
||||
* @param bool $skipGlob if true skip glob content
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function readFromArchive($archiveHandle, $skipGlob = false)
|
||||
{
|
||||
$instance = new static();
|
||||
$startElement = fread($archiveHandle, 3);
|
||||
|
||||
//if ($marker != '?G#') {
|
||||
if ($startElement !== '<G>') {
|
||||
throw new Exception("Invalid glob header marker found {$startElement}. location:" . ftell($archiveHandle));
|
||||
}
|
||||
|
||||
$instance->originalSize = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'OS');
|
||||
$instance->storedSize = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'SS');
|
||||
$instance->hash = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'HA');
|
||||
|
||||
// Skip the </G>
|
||||
fread($archiveHandle, 4);
|
||||
|
||||
if ($skipGlob) {
|
||||
if (fseek($archiveHandle, $instance->storedSize, SEEK_CUR) === -1) {
|
||||
throw new Exception("Can't fseek when skipping glob at location:" . ftell($archiveHandle));
|
||||
}
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get glob content from header
|
||||
*
|
||||
* @param resource $archiveHandle archive hadler
|
||||
* @param self $header chunk glob header
|
||||
* @param bool $isCompressed true if is compressed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function readContent($archiveHandle, self $header, $isCompressed)
|
||||
{
|
||||
if ($header->storedSize == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (($globContents = fread($archiveHandle, $header->storedSize)) === false) {
|
||||
throw new Exception("Error reading glob content");
|
||||
}
|
||||
|
||||
return ($isCompressed ? gzinflate($globContents) : $globContents);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Headers;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Dup archive read header
|
||||
*
|
||||
* Format: #A#{version:5}#{isCompressed}!
|
||||
*/
|
||||
class DupArchiveReaderHeader
|
||||
{
|
||||
/** @var string */
|
||||
protected $version;
|
||||
/** @var bool */
|
||||
public $isCompressed;
|
||||
|
||||
/**
|
||||
* Class Contructor
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
// Prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header from archive
|
||||
*
|
||||
* @param resource $archiveHandle archive resource
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function readFromArchive($archiveHandle)
|
||||
{
|
||||
$instance = new static();
|
||||
$startElement = fgets($archiveHandle, 4);
|
||||
if ($startElement != '<A>') {
|
||||
throw new Exception("Invalid archive header marker found {$startElement}");
|
||||
}
|
||||
|
||||
$instance->version = DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'V');
|
||||
$instance->isCompressed = filter_var(DupArchiveHeaderU::readStandardHeaderField($archiveHandle, 'C'), FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
// Skip the </A>
|
||||
fgets($archiveHandle, 5);
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Info;
|
||||
|
||||
class DupArchiveExpanderInfo
|
||||
{
|
||||
public $archiveHandle = null;
|
||||
public $currentFileHeader = null;
|
||||
public $destDirectory = null;
|
||||
public $directoryWriteCount = 0;
|
||||
public $fileWriteCount = 0;
|
||||
public $isCompressed = false;
|
||||
public $enableWrite = false;
|
||||
|
||||
/**
|
||||
* Get dest path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrentDestFilePath()
|
||||
{
|
||||
if ($this->destDirectory != null) {
|
||||
return "{$this->destDirectory}/{$this->currentFileHeader->relativePath}";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Info;
|
||||
|
||||
class DupArchiveInfo
|
||||
{
|
||||
public $archiveHeader;
|
||||
public $fileHeaders;
|
||||
public $directoryHeaders;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->fileHeaders = array();
|
||||
$this->directoryHeaders = array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Processors;
|
||||
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveDirectoryHeader;
|
||||
use Duplicator\Libs\DupArchive\States\DupArchiveCreateState;
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
|
||||
class DupArchiveDirectoryProcessor
|
||||
{
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param DupArchiveCreateState $createState create state
|
||||
* @param resource $archiveHandle archive resource
|
||||
* @param string $sourceDirectoryPath source directory path
|
||||
* @param string $relativeDirectoryPath relative dirctory path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function writeDirectoryToArchive(
|
||||
DupArchiveCreateState $createState,
|
||||
$archiveHandle,
|
||||
$sourceDirectoryPath,
|
||||
$relativeDirectoryPath
|
||||
) {
|
||||
$directoryHeader = new DupArchiveDirectoryHeader();
|
||||
|
||||
$directoryHeader->permissions = substr(sprintf('%o', fileperms($sourceDirectoryPath)), -4);
|
||||
$directoryHeader->mtime = SnapIO::filemtime($sourceDirectoryPath);
|
||||
$directoryHeader->relativePath = $relativeDirectoryPath;
|
||||
$directoryHeader->relativePathLength = strlen($directoryHeader->relativePath);
|
||||
|
||||
$directoryHeader->writeToArchive($archiveHandle);
|
||||
|
||||
// Just increment this here - the actual state save is on the outside after timeout or completion of all directories
|
||||
$createState->currentDirectoryIndex++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Processors;
|
||||
|
||||
use Duplicator\Libs\DupArchive\DupArchiveEngine;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveDirectoryHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveFileHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveGlobHeader;
|
||||
use Duplicator\Libs\DupArchive\Processors\DupArchiveProcessingFailure;
|
||||
use Duplicator\Libs\DupArchive\States\DupArchiveCreateState;
|
||||
use Duplicator\Libs\DupArchive\States\DupArchiveExpandState;
|
||||
use Duplicator\Libs\DupArchive\Utils\DupArchiveUtil;
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Dup archive file processor
|
||||
*/
|
||||
class DupArchiveFileProcessor
|
||||
{
|
||||
protected static $newFilePathCallback = null;
|
||||
|
||||
/**
|
||||
* Set new file callback
|
||||
*
|
||||
* @param callable $callback callback function
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function setNewFilePathCallback($callback)
|
||||
{
|
||||
if (!is_callable($callback)) {
|
||||
self::$newFilePathCallback = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
self::$newFilePathCallback = $callback;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* get file from relatei path
|
||||
*
|
||||
* @param string $basePath base path
|
||||
* @param string $relativePath relative path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getNewFilePath($basePath, $relativePath)
|
||||
{
|
||||
if (is_null(self::$newFilePathCallback)) {
|
||||
return $basePath . '/' . $relativePath;
|
||||
} else {
|
||||
return call_user_func_array(self::$newFilePathCallback, array($relativePath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write file to archive
|
||||
*
|
||||
* @param DupArchiveCreateState $createState dup archive create state
|
||||
* @param resource $archiveHandle archive resource
|
||||
* @param string $sourceFilepath source file path
|
||||
* @param string $relativeFilePath relative file path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function writeFilePortionToArchive(
|
||||
DupArchiveCreateState $createState,
|
||||
$archiveHandle,
|
||||
$sourceFilepath,
|
||||
$relativeFilePath
|
||||
) {
|
||||
DupArchiveUtil::tlog("writeFileToArchive for {$sourceFilepath}");
|
||||
|
||||
// switching to straight call for speed
|
||||
$sourceHandle = @fopen($sourceFilepath, 'rb');
|
||||
|
||||
if (!is_resource($sourceHandle)) {
|
||||
$createState->archiveOffset = SnapIO::ftell($archiveHandle);
|
||||
$createState->currentFileIndex++;
|
||||
$createState->currentFileOffset = 0;
|
||||
$createState->skippedFileCount++;
|
||||
$createState->addFailure(DupArchiveProcessingFailure::TYPE_FILE, $sourceFilepath, "Couldn't open $sourceFilepath", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($createState->currentFileOffset > 0) {
|
||||
SnapIO::fseek($sourceHandle, $createState->currentFileOffset);
|
||||
} else {
|
||||
$fileHeader = DupArchiveFileHeader::createFromFile($sourceFilepath, $relativeFilePath);
|
||||
$fileHeader->writeToArchive($archiveHandle);
|
||||
}
|
||||
|
||||
$sourceFileSize = filesize($sourceFilepath);
|
||||
|
||||
$moreFileDataToProcess = true;
|
||||
|
||||
while ((!$createState->timedOut()) && $moreFileDataToProcess) {
|
||||
if ($createState->throttleDelayInUs !== 0) {
|
||||
usleep($createState->throttleDelayInUs);
|
||||
}
|
||||
|
||||
$moreFileDataToProcess = self::appendGlobToArchive($createState, $archiveHandle, $sourceHandle, $sourceFilepath, $sourceFileSize);
|
||||
$createState->archiveOffset = SnapIO::ftell($archiveHandle);
|
||||
|
||||
if ($moreFileDataToProcess) {
|
||||
$createState->currentFileOffset += $createState->globSize;
|
||||
} else {
|
||||
$createState->currentFileIndex++;
|
||||
$createState->currentFileOffset = 0;
|
||||
}
|
||||
|
||||
// Only writing state after full group of files have been written - less reliable but more efficient
|
||||
// $createState->save();
|
||||
}
|
||||
|
||||
SnapIO::fclose($sourceHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write file to archive from source
|
||||
*
|
||||
* @param DupArchiveCreateState $createState dup archive create state
|
||||
* @param resource $archiveHandle archive resource
|
||||
* @param string $src source string
|
||||
* @param string $relativeFilePath relative file path
|
||||
* @param int $forceSize if 0 size is auto of content is filled of \0 char to size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function writeFileSrcToArchive(
|
||||
DupArchiveCreateState $createState,
|
||||
$archiveHandle,
|
||||
$src,
|
||||
$relativeFilePath,
|
||||
$forceSize = 0
|
||||
) {
|
||||
DupArchiveUtil::tlog("writeFileSrcToArchive");
|
||||
|
||||
$fileHeader = DupArchiveFileHeader::createFromSrc($src, $relativeFilePath, $forceSize);
|
||||
$fileHeader->writeToArchive($archiveHandle);
|
||||
|
||||
self::appendFileSrcToArchive($createState, $archiveHandle, $src, $forceSize);
|
||||
$createState->currentFileIndex++;
|
||||
$createState->currentFileOffset = 0;
|
||||
$createState->archiveOffset = SnapIO::ftell($archiveHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand du archive
|
||||
*
|
||||
* Assumption is that this is called at the beginning of a glob header since file header already writtern
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState expand state
|
||||
* @param resource $archiveHandle archive resource
|
||||
*
|
||||
* @return bool true on success
|
||||
*/
|
||||
public static function writeToFile(DupArchiveExpandState $expandState, $archiveHandle)
|
||||
{
|
||||
if (isset($expandState->fileRenames[$expandState->currentFileHeader->relativePath])) {
|
||||
$destFilepath = $expandState->fileRenames[$expandState->currentFileHeader->relativePath];
|
||||
} else {
|
||||
$destFilepath = self::getNewFilePath($expandState->basePath, $expandState->currentFileHeader->relativePath);
|
||||
}
|
||||
$parentDir = dirname($destFilepath);
|
||||
|
||||
$moreGlobstoProcess = true;
|
||||
|
||||
SnapIO::dirWriteCheckOrMkdir($parentDir, 'u+rwx', true);
|
||||
|
||||
if ($expandState->currentFileHeader->fileSize > 0) {
|
||||
if ($expandState->currentFileOffset > 0) {
|
||||
$destFileHandle = SnapIO::fopen($destFilepath, 'r+b');
|
||||
SnapIO::fseek($destFileHandle, $expandState->currentFileOffset);
|
||||
} else {
|
||||
$destFileHandle = SnapIO::fopen($destFilepath, 'w+b');
|
||||
}
|
||||
|
||||
while (!$expandState->timedOut()) {
|
||||
$moreGlobstoProcess = $expandState->currentFileOffset < $expandState->currentFileHeader->fileSize;
|
||||
|
||||
if ($moreGlobstoProcess) {
|
||||
if ($expandState->throttleDelayInUs !== 0) {
|
||||
usleep($expandState->throttleDelayInUs);
|
||||
}
|
||||
|
||||
self::appendGlobToFile($expandState, $archiveHandle, $destFileHandle, $destFilepath);
|
||||
|
||||
$expandState->currentFileOffset = ftell($destFileHandle);
|
||||
$expandState->archiveOffset = SnapIO::ftell($archiveHandle);
|
||||
|
||||
$moreGlobstoProcess = $expandState->currentFileOffset < $expandState->currentFileHeader->fileSize;
|
||||
|
||||
if (!$moreGlobstoProcess) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// rsr todo record fclose error
|
||||
@fclose($destFileHandle);
|
||||
$destFileHandle = null;
|
||||
|
||||
if ($expandState->validationType == DupArchiveExpandState::VALIDATION_FULL) {
|
||||
self::validateExpandedFile($expandState);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DupArchiveUtil::tlog('Out of glob loop');
|
||||
|
||||
if ($destFileHandle != null) {
|
||||
// rsr todo record file close error
|
||||
@fclose($destFileHandle);
|
||||
$destFileHandle = null;
|
||||
}
|
||||
|
||||
if (!$moreGlobstoProcess && $expandState->validateOnly && ($expandState->validationType == DupArchiveExpandState::VALIDATION_FULL)) {
|
||||
if (!is_writable($destFilepath)) {
|
||||
SnapIO::chmod($destFilepath, 'u+rw');
|
||||
}
|
||||
if (@unlink($destFilepath) === false) {
|
||||
// $expandState->addFailure(DupArchiveFailureTypes::File, $destFilepath, "Couldn't delete {$destFilepath} during validation", false);
|
||||
// TODO: Have to know how to handle this - want to report it but don’t want to mess up validation -
|
||||
// some non critical errors could be important to validation
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 0 length file so just touch it
|
||||
$moreGlobstoProcess = false;
|
||||
|
||||
if (file_exists($destFilepath)) {
|
||||
@unlink($destFilepath);
|
||||
}
|
||||
|
||||
if (touch($destFilepath) === false) {
|
||||
throw new Exception("Couldn't create {$destFilepath}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!$moreGlobstoProcess) {
|
||||
self::setFileMode($expandState, $destFilepath);
|
||||
self::setFileTimes($expandState, $destFilepath);
|
||||
DupArchiveUtil::tlog('No more globs to process');
|
||||
|
||||
$expandState->fileWriteCount++;
|
||||
$expandState->resetForFile();
|
||||
}
|
||||
|
||||
return !$moreGlobstoProcess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create directory
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState expand state
|
||||
* @param DupArchiveDirectoryHeader $directoryHeader directory header
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function createDirectory(DupArchiveExpandState $expandState, DupArchiveDirectoryHeader $directoryHeader)
|
||||
{
|
||||
/* @var $expandState DupArchiveExpandState */
|
||||
$destDirPath = self::getNewFilePath($expandState->basePath, $directoryHeader->relativePath);
|
||||
|
||||
$mode = $directoryHeader->permissions;
|
||||
|
||||
if ($expandState->directoryModeOverride != -1) {
|
||||
$mode = $expandState->directoryModeOverride;
|
||||
}
|
||||
|
||||
if (!SnapIO::dirWriteCheckOrMkdir($destDirPath, $mode, true)) {
|
||||
$error_message = "Unable to create directory $destDirPath";
|
||||
$expandState->addFailure(DupArchiveProcessingFailure::TYPE_DIRECTORY, $directoryHeader->relativePath, $error_message, false);
|
||||
DupArchiveUtil::tlog($error_message);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set file mode if is enabled
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState dup expand state
|
||||
* @param string $filePath file path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function setFileMode(DupArchiveExpandState $expandState, $filePath)
|
||||
{
|
||||
if ($expandState->fileModeOverride === -1) {
|
||||
return;
|
||||
}
|
||||
return SnapIO::chmod($filePath, $expandState->fileModeOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set original file times if enabled
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState dup expand state
|
||||
* @param string $filePath File path
|
||||
*
|
||||
* @return bool true if success, false otherwise
|
||||
*/
|
||||
protected static function setFileTimes(DupArchiveExpandState $expandState, $filePath)
|
||||
{
|
||||
if (!$expandState->keepFileTime) {
|
||||
return true;
|
||||
}
|
||||
if (!file_exists($filePath)) {
|
||||
return false;
|
||||
}
|
||||
return touch($filePath, $expandState->currentFileHeader->mtime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file entry
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState dup expand state
|
||||
* @param resource $archiveHandle dup archive resource
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function standardValidateFileEntry(DupArchiveExpandState $expandState, $archiveHandle)
|
||||
{
|
||||
$moreGlobstoProcess = $expandState->currentFileOffset < $expandState->currentFileHeader->fileSize;
|
||||
|
||||
if (!$moreGlobstoProcess) {
|
||||
// Not a 'real' write but indicates that we actually did fully process a file in the archive
|
||||
$expandState->fileWriteCount++;
|
||||
} else {
|
||||
while ((!$expandState->timedOut()) && $moreGlobstoProcess) {
|
||||
// Read in the glob header but leave the pointer at the payload
|
||||
$globHeader = DupArchiveGlobHeader::readFromArchive($archiveHandle, false);
|
||||
$globContents = fread($archiveHandle, $globHeader->storedSize);
|
||||
|
||||
if ($globContents === false) {
|
||||
throw new Exception("Error reading glob from archive");
|
||||
}
|
||||
|
||||
$hash = hash('crc32b', $globContents);
|
||||
|
||||
if ($hash != $globHeader->hash) {
|
||||
$expandState->addFailure(
|
||||
DupArchiveProcessingFailure::TYPE_FILE,
|
||||
$expandState->currentFileHeader->relativePath,
|
||||
'Hash mismatch on DupArchive file entry',
|
||||
true
|
||||
);
|
||||
DupArchiveUtil::tlog("Glob hash mismatch during standard check of {$expandState->currentFileHeader->relativePath}");
|
||||
} else {
|
||||
// DupArchiveUtil::tlog("Glob MD5 passes");
|
||||
}
|
||||
|
||||
$expandState->currentFileOffset += $globHeader->originalSize;
|
||||
$expandState->archiveOffset = SnapIO::ftell($archiveHandle);
|
||||
$moreGlobstoProcess = $expandState->currentFileOffset < $expandState->currentFileHeader->fileSize;
|
||||
|
||||
if (!$moreGlobstoProcess) {
|
||||
$expandState->fileWriteCount++;
|
||||
$expandState->resetForFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !$moreGlobstoProcess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState dup expand state
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function validateExpandedFile(DupArchiveExpandState $expandState)
|
||||
{
|
||||
/* @var $expandState DupArchiveExpandState */
|
||||
$destFilepath = self::getNewFilePath($expandState->basePath, $expandState->currentFileHeader->relativePath);
|
||||
|
||||
if ($expandState->currentFileHeader->hash !== '00000000000000000000000000000000') {
|
||||
$hash = hash_file('crc32b', $destFilepath);
|
||||
|
||||
if ($hash !== $expandState->currentFileHeader->hash) {
|
||||
$expandState->addFailure(DupArchiveProcessingFailure::TYPE_FILE, $destFilepath, "MD5 mismatch for {$destFilepath}", false);
|
||||
} else {
|
||||
DupArchiveUtil::tlog('MD5 Match for ' . $destFilepath);
|
||||
}
|
||||
} else {
|
||||
DupArchiveUtil::tlog('MD5 non match is 0\'s');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append file to archive
|
||||
*
|
||||
* @param DupArchiveCreateState $createState create state
|
||||
* @param resource $archiveHandle archive resource
|
||||
* @param resource $sourceFilehandle file resource
|
||||
* @param string $sourceFilepath file path
|
||||
* @param int $fileSize file size
|
||||
*
|
||||
* @return bool true if more file remaning
|
||||
*/
|
||||
private static function appendGlobToArchive(
|
||||
DupArchiveCreateState $createState,
|
||||
$archiveHandle,
|
||||
$sourceFilehandle,
|
||||
$sourceFilepath,
|
||||
$fileSize
|
||||
) {
|
||||
DupArchiveUtil::tlog("Appending file glob to archive for file {$sourceFilepath} at file offset {$createState->currentFileOffset}");
|
||||
|
||||
if ($fileSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fileSize -= $createState->currentFileOffset;
|
||||
$globContents = @fread($sourceFilehandle, $createState->globSize);
|
||||
|
||||
if ($globContents === false) {
|
||||
throw new Exception("Error reading $sourceFilepath");
|
||||
}
|
||||
|
||||
$originalSize = strlen($globContents);
|
||||
|
||||
if ($createState->isCompressed) {
|
||||
$globContents = gzdeflate($globContents, 2); // 2 chosen as best compromise between speed and size
|
||||
$storeSize = strlen($globContents);
|
||||
} else {
|
||||
$storeSize = $originalSize;
|
||||
}
|
||||
|
||||
$globHeader = new DupArchiveGlobHeader();
|
||||
$globHeader->originalSize = $originalSize;
|
||||
$globHeader->storedSize = $storeSize;
|
||||
$globHeader->hash = hash('crc32b', $globContents);
|
||||
$globHeader->writeToArchive($archiveHandle);
|
||||
|
||||
if (@fwrite($archiveHandle, $globContents) === false) {
|
||||
// Considered fatal since we should always be able to write to the archive -
|
||||
// plus the header has already been written (could back this out later though)
|
||||
throw new Exception(
|
||||
"Error writing $sourceFilepath to archive. Ensure site still hasn't run out of space.",
|
||||
DupArchiveEngine::EXCEPTION_FATAL
|
||||
);
|
||||
}
|
||||
|
||||
$fileSizeRemaining = $fileSize - $createState->globSize;
|
||||
$moreFileRemaining = $fileSizeRemaining > 0;
|
||||
|
||||
return $moreFileRemaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append file in dup archvie from source string
|
||||
*
|
||||
* @param DupArchiveCreateState $createState create state
|
||||
* @param resource $archiveHandle archive handle
|
||||
* @param string $src source to add
|
||||
* @param int $forceSize if 0 size is auto of content is filled of \0 char to size
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function appendFileSrcToArchive(
|
||||
DupArchiveCreateState $createState,
|
||||
$archiveHandle,
|
||||
$src,
|
||||
$forceSize = 0
|
||||
) {
|
||||
DupArchiveUtil::tlog("Appending file glob to archive from src");
|
||||
|
||||
if (($originalSize = strlen($src)) == 0 && $forceSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($forceSize == 0 && $createState->isCompressed) {
|
||||
$src = gzdeflate($src, 2); // 2 chosen as best compromise between speed and size
|
||||
$storeSize = strlen($src);
|
||||
} else {
|
||||
$storeSize = $originalSize;
|
||||
}
|
||||
|
||||
if ($forceSize > 0 && $storeSize < $forceSize) {
|
||||
$charsToAdd = $forceSize - $storeSize;
|
||||
$src .= str_repeat("\0", $charsToAdd);
|
||||
$storeSize = $forceSize;
|
||||
}
|
||||
|
||||
$globHeader = new DupArchiveGlobHeader();
|
||||
$globHeader->originalSize = $originalSize;
|
||||
$globHeader->storedSize = $storeSize;
|
||||
$globHeader->hash = hash('crc32b', $src);
|
||||
$globHeader->writeToArchive($archiveHandle);
|
||||
|
||||
|
||||
if (SnapIO::fwriteChunked($archiveHandle, $src) === false) {
|
||||
// Considered fatal since we should always be able to write to the archive -
|
||||
// plus the header has already been written (could back this out later though)
|
||||
throw new Exception(
|
||||
"Error writing SRC to archive. Ensure site still hasn't run out of space.",
|
||||
DupArchiveEngine::EXCEPTION_FATAL
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract file from dup archive
|
||||
* Assumption is that archive handle points to a glob header on this call
|
||||
*
|
||||
* @param DupArchiveExpandState $expandState dup archive expand state
|
||||
* @param resource $archiveHandle archvie resource
|
||||
* @param resource $destFileHandle file resource
|
||||
* @param string $destFilePath file path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function appendGlobToFile(
|
||||
DupArchiveExpandState $expandState,
|
||||
$archiveHandle,
|
||||
$destFileHandle,
|
||||
$destFilePath
|
||||
) {
|
||||
DupArchiveUtil::tlog('Appending file glob to file ' . $destFilePath . ' at file offset ' . $expandState->currentFileOffset);
|
||||
|
||||
// Read in the glob header but leave the pointer at the payload
|
||||
$globHeader = DupArchiveGlobHeader::readFromArchive($archiveHandle, false);
|
||||
if (($globContents = DupArchiveGlobHeader::readContent($archiveHandle, $globHeader, $expandState->archiveHeader->isCompressed)) === false) {
|
||||
throw new Exception("Error reading glob from $destFilePath");
|
||||
}
|
||||
|
||||
if (@fwrite($destFileHandle, $globContents) === false) {
|
||||
throw new Exception("Error writing glob to $destFilePath");
|
||||
} else {
|
||||
DupArchiveUtil::tlog('Successfully wrote glob');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Processors;
|
||||
|
||||
/**
|
||||
*Failure class
|
||||
*/
|
||||
class DupArchiveProcessingFailure
|
||||
{
|
||||
const TYPE_UNKNOWN = 0;
|
||||
const TYPE_FILE = 1;
|
||||
const TYPE_DIRECTORY = 2;
|
||||
|
||||
public $type = self::TYPE_UNKNOWN;
|
||||
public $description = '';
|
||||
public $subject = '';
|
||||
public $isCritical = false;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\States;
|
||||
|
||||
/**
|
||||
* Dup archive create state
|
||||
*/
|
||||
abstract class DupArchiveCreateState extends DupArchiveStateBase
|
||||
{
|
||||
const DEFAULT_GLOB_SIZE = 1048576;
|
||||
|
||||
public $basepathLength = 0;
|
||||
public $currentDirectoryIndex = -1;
|
||||
public $currentFileIndex = -1;
|
||||
public $globSize = self::DEFAULT_GLOB_SIZE;
|
||||
public $newBasePath = null;
|
||||
public $skippedFileCount = 0;
|
||||
public $skippedDirectoryCount = 0;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* State save
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function save();
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\States;
|
||||
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveFileHeader;
|
||||
use Duplicator\Libs\DupArchive\Headers\DupArchiveHeader;
|
||||
|
||||
/**
|
||||
* Dup archive expand state
|
||||
*/
|
||||
abstract class DupArchiveExpandState extends DupArchiveStateBase
|
||||
{
|
||||
const VALIDATION_NONE = 0;
|
||||
const VALIDATION_STANDARD = 1;
|
||||
const VALIDATION_FULL = 2;
|
||||
|
||||
/** @var DupArchiveHeader */
|
||||
public $archiveHeader = null;
|
||||
/** @var DupArchiveFileHeader */
|
||||
public $currentFileHeader = null;
|
||||
public $validateOnly = false;
|
||||
public $validationType = self::VALIDATION_STANDARD;
|
||||
public $fileWriteCount = 0;
|
||||
public $directoryWriteCount = 0;
|
||||
public $expectedFileCount = -1;
|
||||
public $expectedDirectoryCount = -1;
|
||||
public $filteredDirectories = array();
|
||||
public $excludedDirWithoutChilds = array();
|
||||
public $filteredFiles = array();
|
||||
/** @var string[] relative path list to inclue files, overwrite filters */
|
||||
public $includedFiles = array();
|
||||
/** @var string[] relativePath => fullNewPath */
|
||||
public $fileRenames = array();
|
||||
public $directoryModeOverride = -1;
|
||||
public $fileModeOverride = -1;
|
||||
public $lastHeaderOffset = -1;
|
||||
/** @var bool */
|
||||
public $keepFileTime = false;
|
||||
|
||||
/**
|
||||
* Reset state for file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetForFile()
|
||||
{
|
||||
$this->currentFileHeader = null;
|
||||
$this->currentFileOffset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* save expand state
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function save();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\States;
|
||||
|
||||
/**
|
||||
* Simple create state
|
||||
*/
|
||||
class DupArchiveSimpleCreateState extends DupArchiveCreateState
|
||||
{
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->currentDirectoryIndex = 0;
|
||||
$this->currentFileIndex = 0;
|
||||
$this->currentFileOffset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save state
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\States;
|
||||
|
||||
/**
|
||||
* Simple expand state
|
||||
*/
|
||||
class DupArchiveSimpleExpandState extends DupArchiveExpandState
|
||||
{
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* save function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\States;
|
||||
|
||||
use Duplicator\Libs\DupArchive\Processors\DupArchiveProcessingFailure;
|
||||
|
||||
/**
|
||||
* Dup archive state base
|
||||
*/
|
||||
abstract class DupArchiveStateBase
|
||||
{
|
||||
const MAX_FAILURE = 1000;
|
||||
|
||||
public $basePath = '';
|
||||
public $archivePath = '';
|
||||
public $isCompressed = false;
|
||||
public $currentFileOffset = -1;
|
||||
public $archiveOffset = -1;
|
||||
public $timeSliceInSecs = -1;
|
||||
public $working = false;
|
||||
/** @var DupArchiveProcessingFailure[] */
|
||||
public $failures = array();
|
||||
public $failureCount = 0;
|
||||
public $startTimestamp = -1;
|
||||
public $throttleDelayInUs = 0;
|
||||
public $timeoutTimestamp = -1;
|
||||
public $timerEnabled = true;
|
||||
public $isRobust = false;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is present a critical failure
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isCriticalFailurePresent()
|
||||
{
|
||||
if (count($this->failures) > 0) {
|
||||
foreach ($this->failures as $failure) {
|
||||
if ($failure->isCritical) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Che failure summary
|
||||
*
|
||||
* @param boolean $includeCritical include critical failures
|
||||
* @param boolean $includeWarnings include warnings failures
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFailureSummary($includeCritical = true, $includeWarnings = false)
|
||||
{
|
||||
if (count($this->failures) > 0) {
|
||||
$message = '';
|
||||
|
||||
foreach ($this->failures as $failure) {
|
||||
if ($includeCritical || !$failure->isCritical) {
|
||||
$message .= "\n" . $this->getFailureString($failure);
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
} else {
|
||||
if ($includeCritical) {
|
||||
if ($includeWarnings) {
|
||||
return 'No errors or warnings.';
|
||||
} else {
|
||||
return 'No errors.';
|
||||
}
|
||||
} else {
|
||||
return 'No warnings.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return failure string from item
|
||||
*
|
||||
* @param DupArchiveProcessingFailure $failure failure item
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFailureString(DupArchiveProcessingFailure $failure)
|
||||
{
|
||||
$s = '';
|
||||
|
||||
if ($failure->isCritical) {
|
||||
$s = 'CRITICAL: ';
|
||||
}
|
||||
|
||||
return "{$s}{$failure->subject} : {$failure->description}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Add failure item
|
||||
*
|
||||
* @param int $type failure type enum
|
||||
* @param string $subject failure subject
|
||||
* @param string $description failure description
|
||||
* @param boolean $isCritical true if is critical
|
||||
*
|
||||
* @return DupArchiveProcessingFailure
|
||||
*/
|
||||
public function addFailure($type, $subject, $description, $isCritical = true)
|
||||
{
|
||||
$this->failureCount++;
|
||||
if ($this->failureCount > self::MAX_FAILURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$failure = new DupArchiveProcessingFailure();
|
||||
|
||||
$failure->type = $type;
|
||||
$failure->subject = $subject;
|
||||
$failure->description = $description;
|
||||
$failure->isCritical = $isCritical;
|
||||
|
||||
$this->failures[] = $failure;
|
||||
|
||||
return $failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set start time
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function startTimer()
|
||||
{
|
||||
if ($this->timerEnabled) {
|
||||
$this->timeoutTimestamp = time() + $this->timeSliceInSecs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is timeout
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function timedOut()
|
||||
{
|
||||
if ($this->timerEnabled) {
|
||||
if ($this->timeoutTimestamp != -1) {
|
||||
return time() >= $this->timeoutTimestamp;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Utils;
|
||||
|
||||
use Duplicator\Libs\Snap\SnapJson;
|
||||
use Exception;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Description of class
|
||||
*
|
||||
* @author Robert
|
||||
*/
|
||||
class DupArchiveScanUtil
|
||||
{
|
||||
/**
|
||||
* Get scan
|
||||
*
|
||||
* @param string $scanFilepath scan file path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function getScan($scanFilepath)
|
||||
{
|
||||
DupArchiveUtil::tlog("Getting scen");
|
||||
$scan_handle = fopen($scanFilepath, 'r');
|
||||
|
||||
if ($scan_handle === false) {
|
||||
throw new Exception("Can't open {$scanFilepath}");
|
||||
}
|
||||
|
||||
$scan_file = fread($scan_handle, filesize($scanFilepath));
|
||||
|
||||
if ($scan_file === false) {
|
||||
throw new Exception("Can't read from {$scanFilepath}");
|
||||
}
|
||||
|
||||
$scan = json_decode($scan_file);
|
||||
if (!$scan) {
|
||||
throw new Exception("Error decoding scan file");
|
||||
}
|
||||
|
||||
fclose($scan_handle);
|
||||
|
||||
return $scan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get scan object
|
||||
*
|
||||
* @param string $sourceDirectory folder to scan
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public static function createScanObject($sourceDirectory)
|
||||
{
|
||||
$scan = new stdClass();
|
||||
|
||||
$scan->Dirs = DupArchiveUtil::expandDirectories($sourceDirectory, true);
|
||||
$scan->Files = DupArchiveUtil::expandFiles($sourceDirectory, true);
|
||||
|
||||
return $scan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan folder and add result to scan file
|
||||
*
|
||||
* @param string $scanFilepath scan file
|
||||
* @param string $sourceDirectory folder to scan
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function createScan($scanFilepath, $sourceDirectory)
|
||||
{
|
||||
DupArchiveUtil::tlog("Creating scan");
|
||||
|
||||
$scan = self::createScanObject($sourceDirectory);
|
||||
$scan_handle = fopen($scanFilepath, 'w');
|
||||
|
||||
if ($scan_handle === false) {
|
||||
echo "Couldn't create scan file";
|
||||
die();
|
||||
}
|
||||
|
||||
$jsn = SnapJson::jsonEncode($scan);
|
||||
|
||||
fwrite($scan_handle, $jsn);
|
||||
return $scan;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\DupArchive\Utils;
|
||||
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
|
||||
class DupArchiveUtil
|
||||
{
|
||||
public static $TRACE_ON = false; //rodo rework this
|
||||
public static $logger = null;
|
||||
|
||||
/**
|
||||
* get file list
|
||||
*
|
||||
* @param string $base_dir folder to check
|
||||
* @param bool $recurse true for recursive scan
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function expandFiles($base_dir, $recurse)
|
||||
{
|
||||
$files = array();
|
||||
|
||||
foreach (scandir($base_dir) as $file) {
|
||||
if (($file == '.') || ($file == '..')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = "{$base_dir}/{$file}";
|
||||
|
||||
if (is_file($file)) {
|
||||
$files [] = $file;
|
||||
} elseif (is_dir($file) && $recurse) {
|
||||
$files = array_merge($files, self::expandFiles($file, $recurse));
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* get folder list
|
||||
*
|
||||
* @param string $base_dir folder to check
|
||||
* @param bool $recurse true for recursive scan
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function expandDirectories($base_dir, $recurse)
|
||||
{
|
||||
$directories = array();
|
||||
|
||||
foreach (scandir($base_dir) as $candidate) {
|
||||
if (($candidate == '.') || ($candidate == '..')) {
|
||||
continue;
|
||||
}
|
||||
$candidate = "{$base_dir}/{$candidate}";
|
||||
|
||||
if (is_dir($candidate)) {
|
||||
$directories[] = $candidate;
|
||||
if ($recurse) {
|
||||
$directories = array_merge($directories, self::expandDirectories($candidate, $recurse));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $directories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write $s in log
|
||||
*
|
||||
* @param string $s log string
|
||||
* @param boolean $flush if true flosh name
|
||||
* @param string $callingFunctionName function has called log
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function log($s, $flush = false, $callingFunctionName = null)
|
||||
{
|
||||
if (self::$logger != null) {
|
||||
if ($callingFunctionName === null) {
|
||||
$callingFunctionName = SnapUtil::getCallingFunctionName();
|
||||
}
|
||||
|
||||
self::$logger->log($s, $flush, $callingFunctionName);
|
||||
} else {
|
||||
// throw new Exception('Logging object not initialized');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write trace log
|
||||
*
|
||||
* @param string $s log string
|
||||
* @param boolean $flush if true flosh name
|
||||
* @param string $callingFunctionName function has called log
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function tlog($s, $flush = false, $callingFunctionName = null)
|
||||
{
|
||||
if (self::$TRACE_ON) {
|
||||
if ($callingFunctionName === null) {
|
||||
$callingFunctionName = SnapUtil::getCallingFunctionName();
|
||||
}
|
||||
|
||||
self::log("####{$s}", $flush, $callingFunctionName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write object in trace log
|
||||
*
|
||||
* @param string $s log string
|
||||
* @param mixed $o value to write in log
|
||||
* @param boolean $flush if true flosh name
|
||||
* @param string $callingFunctionName function has called log
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function tlogObject($s, $o, $flush = false, $callingFunctionName = null)
|
||||
{
|
||||
if (is_object($o)) {
|
||||
$o = get_object_vars($o);
|
||||
}
|
||||
|
||||
$ostring = print_r($o, true);
|
||||
|
||||
if ($callingFunctionName === null) {
|
||||
$callingFunctionName = SnapUtil::getCallingFunctionName();
|
||||
}
|
||||
|
||||
self::tlog($s, $flush, $callingFunctionName);
|
||||
self::tlog($ostring, $flush, $callingFunctionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write object in log
|
||||
*
|
||||
* @param string $s log string
|
||||
* @param mixed $o value to write in log
|
||||
* @param boolean $flush if true flosh name
|
||||
* @param string $callingFunctionName function has called log
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function logObject($s, $o, $flush = false, $callingFunctionName = null)
|
||||
{
|
||||
$ostring = print_r($o, true);
|
||||
|
||||
if ($callingFunctionName === null) {
|
||||
$callingFunctionName = SnapUtil::getCallingFunctionName();
|
||||
}
|
||||
|
||||
self::log($s, $flush, $callingFunctionName);
|
||||
self::log($ostring, $flush, $callingFunctionName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
*
|
||||
* phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols
|
||||
* phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\OneClickUpgrade;
|
||||
|
||||
use WP_Upgrader_Skin;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Silent upgrader skin for one-click upgrade
|
||||
*
|
||||
* @since 1.5.13
|
||||
*/
|
||||
class UpgraderSkin extends WP_Upgrader_Skin
|
||||
{
|
||||
/**
|
||||
* Primary class constructor.
|
||||
*
|
||||
* @since 1.5.13
|
||||
*
|
||||
* @param array $args Empty array of args (we will use defaults).
|
||||
*/
|
||||
public function __construct($args = array())
|
||||
{
|
||||
parent::__construct($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upgrader object and store it as a property in the parent class.
|
||||
*
|
||||
* @since 1.5.13
|
||||
*
|
||||
* @param object $upgrader The upgrader object (passed by reference).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_upgrader(&$upgrader)
|
||||
{
|
||||
if (is_object($upgrader)) {
|
||||
$this->upgrader =& $upgrader;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upgrader result and store it as a property in the parent class.
|
||||
*
|
||||
* @since 1.5.13
|
||||
*
|
||||
* @param object $result The result of the install process.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_result($result)
|
||||
{
|
||||
$this->result = $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty out the header of its HTML content and only check to see if it has
|
||||
* been performed or not.
|
||||
*
|
||||
* @since 1.5.13
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function header()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty out the footer of its HTML contents.
|
||||
*
|
||||
* @since 1.5.13
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function footer()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Instead of outputting HTML for errors, send proper WordPress AJAX error response.
|
||||
*
|
||||
* @since 1.5.13
|
||||
*
|
||||
* @param array $errors Array of errors with the install process.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function error($errors)
|
||||
{
|
||||
if (!empty($errors)) {
|
||||
wp_send_json_error(array('message' => esc_html__('There was an error installing the upgrade. Please try again.', 'duplicator')));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty out the feedback method to prevent outputting HTML strings as the install
|
||||
* is progressing.
|
||||
*
|
||||
* @since 1.5.13
|
||||
*
|
||||
* @param string $string The feedback string.
|
||||
* @param mixed ...$args Additional arguments.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function feedback($string, ...$args)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Functionality to check
|
||||
*/
|
||||
class FunctionalityCheck
|
||||
{
|
||||
const TYPE_FUNCTION = 1;
|
||||
const TYPE_CLASS = 2;
|
||||
|
||||
/** @var int Enum type */
|
||||
protected $type = 0;
|
||||
/** @var string item key to test */
|
||||
protected $itemKey = '';
|
||||
/** @var bool true if this item is required */
|
||||
protected $required = false;
|
||||
/** @var string link to documntation */
|
||||
public $link = '';
|
||||
/** @var string html troubleshoot */
|
||||
public $troubleshoot = '';
|
||||
/** @var ?callable if is set is called when check fail */
|
||||
protected $failCallback = null;
|
||||
|
||||
|
||||
/**
|
||||
* Class contructor
|
||||
*
|
||||
* @param int $type Enum type
|
||||
* @param string $key item key to test
|
||||
* @param bool $required true if this item is required
|
||||
* @param string $link link to documntation
|
||||
* @param string $troubleshoot html troubleshoot
|
||||
*/
|
||||
public function __construct($type, $key, $required = false, $link = '', $troubleshoot = '')
|
||||
{
|
||||
switch ($type) {
|
||||
case self::TYPE_FUNCTION:
|
||||
case self::TYPE_CLASS:
|
||||
$this->type = $type;
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid item type');
|
||||
}
|
||||
|
||||
if (strlen($key) == 0) {
|
||||
throw new Exception('Key can\'t be empty');
|
||||
}
|
||||
$this->required = $required;
|
||||
$this->itemKey = (string) $key;
|
||||
$this->link = (string) $link;
|
||||
$this->troubleshoot = (string) $troubleshoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of type
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of itemKey
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getItemKey()
|
||||
{
|
||||
return $this->itemKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* true if is required
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRequired()
|
||||
{
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if item exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$result = false;
|
||||
|
||||
switch ($this->type) {
|
||||
case self::TYPE_FUNCTION:
|
||||
$result = function_exists($this->itemKey);
|
||||
break;
|
||||
case self::TYPE_CLASS:
|
||||
$result = SnapUtil::classExists($this->itemKey);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid item type');
|
||||
}
|
||||
|
||||
if ($result == false && is_callable($this->failCallback)) {
|
||||
call_user_func($this->failCallback, $this);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of failCallback
|
||||
*
|
||||
* @param callable $failCallback fail callback function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setFailCallback($failCallback)
|
||||
{
|
||||
$this->failCallback = $failCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all Functionalities in list
|
||||
*
|
||||
* @param self[] $funcs Functionalities list
|
||||
* @param bool $requiredOnly if true skip functs not required
|
||||
* @param self[] $notPassList list of items that not have pass the test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkList($funcs, $requiredOnly = false, &$notPassList = array())
|
||||
{
|
||||
if (!is_array($funcs)) {
|
||||
throw new Exception('funcs must be an array');
|
||||
}
|
||||
|
||||
$notPassList = array();
|
||||
|
||||
foreach ($funcs as $func) {
|
||||
if ($requiredOnly && !$func->isRequired()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($func->check() === false) {
|
||||
$notPassList[] = $func;
|
||||
}
|
||||
}
|
||||
|
||||
return (count($notPassList) === 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*
|
||||
* this file isn't under PSR4 autoloader standard
|
||||
*/
|
||||
|
||||
if (!interface_exists('JsonSerializable')) {
|
||||
if (!defined('WP_JSON_SERIALIZE_COMPATIBLE')) {
|
||||
define('WP_JSON_SERIALIZE_COMPATIBLE', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* JsonSerializable interface.
|
||||
*
|
||||
* Compatibility shim for PHP <5.4
|
||||
*/
|
||||
interface JsonSerializable // phpcs:ignore
|
||||
{
|
||||
/**
|
||||
* Serialize object
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function jsonSerialize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap\JsonSerialize;
|
||||
|
||||
// phpcs:disable
|
||||
require_once(dirname(__DIR__) . '/JsonSerializable.php');
|
||||
// phpcs:enable
|
||||
|
||||
/**
|
||||
* Abstract class to extend in order to use the maximum potentialities of JsonSerialize
|
||||
*/
|
||||
// phpcs:ignore PHPCompatibility.Interfaces.NewInterfaces.jsonserializableFound
|
||||
abstract class AbstractJsonSerializable extends AbstractJsonSerializeObjData implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Prepared json serialized object
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
final public function jsonSerialize()
|
||||
{
|
||||
return self::objectToJsonData($this, 0, array());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap\JsonSerialize;
|
||||
|
||||
use Exception;
|
||||
use ReflectionClass;
|
||||
use ReflectionObject;
|
||||
|
||||
/**
|
||||
* This calsse contains the logic that converts objects into values ready to be encoded in json
|
||||
*/
|
||||
abstract class AbstractJsonSerializeObjData
|
||||
{
|
||||
const CLASS_KEY_FOR_JSON_SERIALIZE = 'CL_-=_-=';
|
||||
const JSON_SERIALIZE_SKIP_CLASS_NAME = 1073741824; // 30 bit mask
|
||||
|
||||
/**
|
||||
* Convert object to array with private and protected proprieties.
|
||||
* Private parent class proprieties aren't considered.
|
||||
*
|
||||
* @param object $obj obejct to serialize
|
||||
* @param int $flags flags bitmask
|
||||
* @param string[] $objParents objs parents unique objects hash list
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
final protected static function objectToJsonData($obj, $flags = 0, $objParents = array())
|
||||
{
|
||||
$reflect = new ReflectionObject($obj);
|
||||
if (!($flags & self::JSON_SERIALIZE_SKIP_CLASS_NAME)) {
|
||||
$result = array(self::CLASS_KEY_FOR_JSON_SERIALIZE => $reflect->name);
|
||||
}
|
||||
|
||||
if (method_exists($obj, '__sleep')) {
|
||||
$includeProps = $obj->__sleep();
|
||||
if (!is_array($includeProps)) {
|
||||
throw new Exception('__sleep method must return an array');
|
||||
}
|
||||
} else {
|
||||
$includeProps = true;
|
||||
}
|
||||
|
||||
// Get all props of current class but not props private of parent class and static props
|
||||
foreach ($reflect->getProperties() as $prop) {
|
||||
if ($prop->isStatic()) {
|
||||
continue;
|
||||
}
|
||||
$propName = $prop->getName();
|
||||
if ($includeProps !== true && !in_array($propName, $includeProps)) {
|
||||
continue;
|
||||
}
|
||||
$prop->setAccessible(true);
|
||||
$propValue = $prop->getValue($obj);
|
||||
$result[$propName] = self::valueToJsonData($propValue, $flags, $objParents);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive parse values, all objects are transformed to array
|
||||
*
|
||||
* @param mixed $value valute to parse
|
||||
* @param int $flags flags bitmask
|
||||
* @param string[] $objParents objs parents unique hash ids
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
final public static function valueToJsonData($value, $flags = 0, $objParents = array())
|
||||
{
|
||||
switch (gettype($value)) {
|
||||
case "boolean":
|
||||
case "integer":
|
||||
case "double":
|
||||
case "string":
|
||||
case "NULL":
|
||||
return $value;
|
||||
case "array":
|
||||
$result = array();
|
||||
foreach ($value as $key => $arrayVal) {
|
||||
$result[$key] = self::valueToJsonData($arrayVal, $flags, $objParents);
|
||||
}
|
||||
return $result;
|
||||
case "object":
|
||||
$objHash = spl_object_hash($value);
|
||||
if (in_array($objHash, $objParents)) {
|
||||
// prevent infinite recursion loop
|
||||
return null;
|
||||
}
|
||||
$objParents[] = $objHash;
|
||||
return self::objectToJsonData($value, $flags, $objParents);
|
||||
case "resource":
|
||||
case "resource (closed)":
|
||||
case "unknown type":
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value from json decoded data
|
||||
*
|
||||
* @param mixed $value json decoded data
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
final protected static function jsonDataToValue($value)
|
||||
{
|
||||
switch (gettype($value)) {
|
||||
case 'array':
|
||||
if (($newClassName = self::getClassFromArray($value)) === false) {
|
||||
$result = array();
|
||||
foreach ($value as $key => $arrayVal) {
|
||||
$result[$key] = self::jsonDataToValue($arrayVal);
|
||||
}
|
||||
} else {
|
||||
$result = self::fillObjFromValue($value, self::getObjFromClass($newClassName));
|
||||
}
|
||||
return $result;
|
||||
case 'boolean':
|
||||
case 'integer':
|
||||
case 'double':
|
||||
case 'string':
|
||||
case "NULL":
|
||||
return $value;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object from class name, if class don't exists return StdClass.
|
||||
* With PHP 5.4.0 the object is intialized without call the constructor.
|
||||
*
|
||||
* @param string $class class name
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
final protected static function getObjFromClass($class)
|
||||
{
|
||||
if (class_exists($class)) {
|
||||
if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
|
||||
$classReflect = new ReflectionClass($class);
|
||||
return $classReflect->newInstanceWithoutConstructor();
|
||||
} else {
|
||||
return new $class();
|
||||
}
|
||||
} else {
|
||||
return new \StdClass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill passed object from array values
|
||||
*
|
||||
* @param array $value value from json data
|
||||
* @param object $obj object to fill with json data
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
final protected static function fillObjFromValue($value, $obj)
|
||||
{
|
||||
if ($obj instanceof \stdClass) {
|
||||
foreach ($value as $arrayProp => $arrayValue) {
|
||||
if ($arrayProp == self::CLASS_KEY_FOR_JSON_SERIALIZE) {
|
||||
continue;
|
||||
}
|
||||
$obj->{$arrayProp} = self::jsonDataToValue($arrayValue);
|
||||
}
|
||||
} else {
|
||||
$reflect = new ReflectionObject($obj);
|
||||
foreach ($reflect->getProperties() as $prop) {
|
||||
$prop->setAccessible(true);
|
||||
$propName = $prop->getName();
|
||||
if (!isset($value[$propName]) || $prop->isStatic()) {
|
||||
continue;
|
||||
}
|
||||
$prop->setValue($obj, self::jsonDataToValue($value[$propName]));
|
||||
}
|
||||
|
||||
if (method_exists($obj, '__wakeup')) {
|
||||
$obj->__wakeup();
|
||||
}
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return class name from array values
|
||||
*
|
||||
* @param array $array array data
|
||||
*
|
||||
* @return bool|string false if prop not found
|
||||
*/
|
||||
final protected static function getClassFromArray($array)
|
||||
{
|
||||
return (isset($array[self::CLASS_KEY_FOR_JSON_SERIALIZE]) ? $array[self::CLASS_KEY_FOR_JSON_SERIALIZE] : false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap\JsonSerialize;
|
||||
|
||||
use Duplicator\Libs\Snap\SnapJson;
|
||||
use Duplicator\Libs\Snap\SnapLog;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* This class serializes and deserializes a variable in json keeping the class type and saving also private objects
|
||||
*/
|
||||
class JsonSerialize extends AbstractJsonSerializeObjData
|
||||
{
|
||||
/**
|
||||
* Return json string
|
||||
*
|
||||
* @param mixed $value value to serialize
|
||||
* @param integer $flags json_encode flags
|
||||
* @param integer $depth json_encode depth
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.json-encode.php
|
||||
*
|
||||
* @return string|bool Returns a JSON encoded string on success or false on failure.
|
||||
*/
|
||||
public static function serialize($value, $flags = 0, $depth = 512)
|
||||
{
|
||||
return SnapJson::jsonEncode(self::valueToJsonData($value, $flags), $flags, $depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize from json
|
||||
*
|
||||
* @param string $json json string
|
||||
* @param integer $depth json_decode depth
|
||||
* @param integer $flags json_decode flags
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.json-decode.php
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function unserialize($json, $depth = 512, $flags = 0)
|
||||
{
|
||||
// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.json_decode_optionsFound
|
||||
$publicArray = (version_compare(PHP_VERSION, '5.4', '>=') ? json_decode($json, true, $depth, $flags) : json_decode($json, true, $depth)
|
||||
);
|
||||
return self::jsonDataToValue($publicArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize json on passed object
|
||||
*
|
||||
* @param string $json json string
|
||||
* @param object|string $obj object to fill or class name
|
||||
* @param integer $depth json_decode depth
|
||||
* @param integer $flags json_decode flags
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.json-decode.php
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function unserializeToObj($json, $obj, $depth = 512, $flags = 0)
|
||||
{
|
||||
if (is_object($obj)) {
|
||||
} elseif (is_string($obj) && class_exists($obj)) {
|
||||
$obj = self::getObjFromClass($obj);
|
||||
} else {
|
||||
throw new Exception('invalid obj param');
|
||||
}
|
||||
// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.json_decode_optionsFound
|
||||
$value = (version_compare(PHP_VERSION, '5.4', '>=') ? json_decode($json, true, $depth, $flags) : json_decode($json, true, $depth)
|
||||
);
|
||||
if (!is_array($value)) {
|
||||
throw new Exception('json value isn\'t an array VALUE: ' . SnapLog::v2str($value));
|
||||
}
|
||||
return self::fillObjFromValue($value, $obj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
class Snap32BitSizeLimitException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Snap code generator utils
|
||||
*/
|
||||
class SnapCode
|
||||
{
|
||||
/**
|
||||
* Get class code from file
|
||||
*
|
||||
* @param string $file file path
|
||||
* @param bool $wrapNamespace if true wrap name space with brackets
|
||||
* @param bool $removeFirstPHPTag if true removes opening php tah
|
||||
* @param bool $removeBalnkLines if true remove blank lines
|
||||
* @param bool $removeComments if true remove comments
|
||||
* @param bool $required if true and file can't be read then throw and exception else return empty string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSrcClassCode(
|
||||
$file,
|
||||
$wrapNamespace = true,
|
||||
$removeFirstPHPTag = false,
|
||||
$removeBalnkLines = true,
|
||||
$removeComments = true,
|
||||
$required = true
|
||||
) {
|
||||
if (!is_file($file) || !is_readable($file)) {
|
||||
if ($required) {
|
||||
throw new Exception('Code file "' . $file . '" don\'t exists');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
if (($src = file_get_contents($file)) === false) {
|
||||
if ($required) {
|
||||
throw new Exception('Can\'t read code file "' . $file . '"');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($removeFirstPHPTag) {
|
||||
$src = preg_replace('/^(<\?php)/', "", $src);
|
||||
}
|
||||
|
||||
if ($wrapNamespace) {
|
||||
$src = preg_replace('/(.*^\s*)(namespace.*?)(;)(.*)/sm', "$2 {\n$4}", $src);
|
||||
}
|
||||
|
||||
if ($removeComments) {
|
||||
$src = preg_replace('/^\s*\/\*.*?\*\//sm', '', $src);
|
||||
$src = preg_replace('/^\s*\/\/.*$/m', '', $src);
|
||||
}
|
||||
|
||||
if ($removeBalnkLines) {
|
||||
$src = preg_replace('/\n\s*\n/s', "\n", $src);
|
||||
}
|
||||
|
||||
return $src;
|
||||
}
|
||||
}
|
||||
734
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapDB.php
Normal file
734
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapDB.php
Normal file
@@ -0,0 +1,734 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
use Exception;
|
||||
use mysqli;
|
||||
use mysqli_result;
|
||||
|
||||
class SnapDB
|
||||
{
|
||||
const CONN_MYSQL = 'mysql';
|
||||
const CONN_MYSQLI = 'mysqli';
|
||||
const CACHE_PREFIX_PRIMARY_KEY_COLUMN = 'pkcol_';
|
||||
const DB_ENGINE_MYSQL = 'MySQL';
|
||||
const DB_ENGINE_MARIA = 'MariaDB';
|
||||
const DB_ENGINE_PERCONA = 'Percona';
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
private static $cache = array();
|
||||
|
||||
/**
|
||||
* Return array if primary key is composite key
|
||||
*
|
||||
* @param mysqli|resource $dbh database connection
|
||||
* @param string $tableName table name
|
||||
* @param null|callable $logCallback log callback
|
||||
*
|
||||
* @return false|string|string[] return unique index column ky or false if don't exists
|
||||
*/
|
||||
public static function getUniqueIndexColumn($dbh, $tableName, $logCallback = null)
|
||||
{
|
||||
$cacheKey = self::CACHE_PREFIX_PRIMARY_KEY_COLUMN . $tableName;
|
||||
|
||||
if (!isset(self::$cache[$cacheKey])) {
|
||||
$query = 'SHOW COLUMNS FROM `' . self::realEscapeString($dbh, $tableName) . '` WHERE `Key` IN ("PRI","UNI")';
|
||||
if (($result = self::query($dbh, $query)) === false) {
|
||||
if (is_callable($logCallback)) {
|
||||
call_user_func($logCallback, $dbh, $result, $query);
|
||||
}
|
||||
throw new \Exception('SHOW KEYS QUERY ERROR: ' . self::error($dbh));
|
||||
}
|
||||
|
||||
if (is_callable($logCallback)) {
|
||||
call_user_func($logCallback, $dbh, $result, $query);
|
||||
}
|
||||
|
||||
if (self::numRows($result) == 0) {
|
||||
self::$cache[$cacheKey] = false;
|
||||
} else {
|
||||
$primary = false;
|
||||
$excludePrimary = false;
|
||||
$unique = false;
|
||||
|
||||
while ($row = self::fetchAssoc($result)) {
|
||||
switch ($row['Key']) {
|
||||
case 'PRI':
|
||||
if ($primary === false) {
|
||||
$primary = $row['Field'];
|
||||
} else {
|
||||
if (is_scalar($primary)) {
|
||||
$primary = array($primary);
|
||||
}
|
||||
$primary[] = $row['Field'];
|
||||
}
|
||||
|
||||
if (preg_match('/^(?:var)?binary/i', $row['Type'])) {
|
||||
// exclude binary or varbynary columns
|
||||
$excludePrimary = true;
|
||||
}
|
||||
break;
|
||||
case 'UNI':
|
||||
if (!preg_match('/^(?:var)?binary/i', $row['Type'])) {
|
||||
// exclude binary or varbynary columns
|
||||
$unique = $row['Field'];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($primary !== false && $excludePrimary === false) {
|
||||
self::$cache[$cacheKey] = $primary;
|
||||
} elseif ($unique !== false) {
|
||||
self::$cache[$cacheKey] = $unique;
|
||||
} else {
|
||||
self::$cache[$cacheKey] = false;
|
||||
}
|
||||
}
|
||||
self::freeResult($result);
|
||||
}
|
||||
|
||||
return self::$cache[$cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape the regex for mysql queries, the mysqli_real_escape must be applied anyway to the generated string
|
||||
*
|
||||
* @param string $regex Regex
|
||||
*
|
||||
* @return string Escaped regex
|
||||
*/
|
||||
public static function quoteRegex($regex)
|
||||
{
|
||||
// preg_quote takes a string and escapes special characters with a backslash.
|
||||
// It is meant for PHP regexes, not MySQL regexes, and it does not escape &,
|
||||
// which is needed for MySQL. So we only need to modify it like so:
|
||||
// https://stackoverflow.com/questions/3782379/whats-the-best-way-to-escape-user-input-for-regular-expressions-in-mysql
|
||||
return preg_replace('/&/', '\\&', preg_quote($regex, null /* no delimiter */));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset from the current row
|
||||
*
|
||||
* @param mixed[] $row current database row
|
||||
* @param int|string|string[] $indexColumns columns of the row that generated the index offset
|
||||
* @param mixed $lastOffset last offset
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getOffsetFromRowAssoc($row, $indexColumns, $lastOffset)
|
||||
{
|
||||
if (is_array($indexColumns)) {
|
||||
$result = array();
|
||||
foreach ($indexColumns as $col) {
|
||||
$result[$col] = isset($row[$col]) ? $row[$col] : 0;
|
||||
}
|
||||
return $result;
|
||||
} elseif (strlen($indexColumns) > 0) {
|
||||
return isset($row[$indexColumns]) ? $row[$indexColumns] : 0;
|
||||
} else {
|
||||
if (is_scalar($lastOffset)) {
|
||||
return $lastOffset + 1;
|
||||
} else {
|
||||
return $lastOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function performs a select by structuring the primary key as offset if the table has a primary key.
|
||||
* For optimization issues, no checks are performed on the input query and it is assumed that the select has at least a where value.
|
||||
* If there are no conditions, you still have to perform an always true condition, for example
|
||||
* SELECT * FROM `copy1_postmeta` WHERE 1
|
||||
*
|
||||
* @param mysqli|resource $dbh database connection
|
||||
* @param string $query query string
|
||||
* @param string $table table name
|
||||
* @param int $offset row offset
|
||||
* @param int $limit limit of query, 0 no limit
|
||||
* @param mixed $lastRowOffset last offset to use on next function call
|
||||
* @param null|callable $logCallback log callback
|
||||
*
|
||||
* @return mysqli_result
|
||||
*/
|
||||
public static function selectUsingPrimaryKeyAsOffset($dbh, $query, $table, $offset, $limit, &$lastRowOffset = null, $logCallback = null)
|
||||
{
|
||||
$where = '';
|
||||
$orderby = '';
|
||||
$offsetStr = '';
|
||||
$limitStr = $limit > 0 ? ' LIMIT ' . $limit : '';
|
||||
|
||||
if (($primaryColumn = self::getUniqueIndexColumn($dbh, $table, $logCallback)) == false) {
|
||||
$offsetStr = ' OFFSET ' . (is_scalar($offset) ? $offset : 0);
|
||||
} else {
|
||||
if (is_array($primaryColumn)) {
|
||||
// COMPOSITE KEY
|
||||
$orderByCols = array();
|
||||
foreach ($primaryColumn as $colIndex => $col) {
|
||||
$orderByCols[] = '`' . $col . '` ASC';
|
||||
}
|
||||
$orderby = ' ORDER BY ' . implode(',', $orderByCols);
|
||||
} else {
|
||||
$orderby = ' ORDER BY `' . $primaryColumn . '` ASC';
|
||||
}
|
||||
$where = self::getOffsetKeyCondition($dbh, $primaryColumn, $offset);
|
||||
}
|
||||
$query .= $where . $orderby . $limitStr . $offsetStr;
|
||||
|
||||
if (($result = self::query($dbh, $query)) === false) {
|
||||
if (is_callable($logCallback)) {
|
||||
call_user_func($logCallback, $dbh, $result, $query);
|
||||
}
|
||||
throw new \Exception('SELECT ERROR: ' . self::error($dbh) . ' QUERY: ' . $query);
|
||||
}
|
||||
|
||||
if (is_callable($logCallback)) {
|
||||
call_user_func($logCallback, $dbh, $result, $query);
|
||||
}
|
||||
|
||||
if (self::dbConnTypeByResult($result) === self::CONN_MYSQLI) {
|
||||
if ($primaryColumn == false) {
|
||||
$lastRowOffset = $offset + $result->num_rows;
|
||||
} else {
|
||||
if ($result->num_rows == 0) {
|
||||
$lastRowOffset = $offset;
|
||||
} else {
|
||||
$result->data_seek(($result->num_rows - 1));
|
||||
$row = $result->fetch_assoc();
|
||||
if (is_array($primaryColumn)) {
|
||||
$lastRowOffset = array();
|
||||
foreach ($primaryColumn as $col) {
|
||||
$lastRowOffset[$col] = $row[$col];
|
||||
}
|
||||
} else {
|
||||
$lastRowOffset = $row[$primaryColumn];
|
||||
}
|
||||
$result->data_seek(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($primaryColumn == false) {
|
||||
$lastRowOffset = $offset + mysql_num_rows($result); // @phpstan-ignore-line
|
||||
} else {
|
||||
if (mysql_num_rows($result) == 0) { // @phpstan-ignore-line
|
||||
$lastRowOffset = $offset;
|
||||
} else {
|
||||
mysql_data_seek($result, (mysql_num_rows($result) - 1)); // @phpstan-ignore-line
|
||||
$row = mysql_fetch_assoc($result); // @phpstan-ignore-line
|
||||
if (is_array($primaryColumn)) {
|
||||
$lastRowOffset = array();
|
||||
foreach ($primaryColumn as $col) {
|
||||
$lastRowOffset[$col] = $row[$col];
|
||||
}
|
||||
} else {
|
||||
$lastRowOffset = $row[$primaryColumn];
|
||||
}
|
||||
mysql_data_seek($result, 0); // @phpstan-ignore-line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Depending on the structure type of the primary key returns the condition to position at the right offset
|
||||
*
|
||||
* @param mysqli|resource $dbh database connection
|
||||
* @param string|string[] $primaryColumn primaricolumng index
|
||||
* @param mixed $offset offset
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getOffsetKeyCondition($dbh, $primaryColumn, $offset)
|
||||
{
|
||||
$condition = '';
|
||||
|
||||
if ($offset === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// COUPOUND KEY
|
||||
if (is_array($primaryColumn)) {
|
||||
$isFirstCond = true;
|
||||
|
||||
foreach ($primaryColumn as $colIndex => $col) {
|
||||
if (is_array($offset) && isset($offset[$col])) {
|
||||
if ($isFirstCond) {
|
||||
$isFirstCond = false;
|
||||
} else {
|
||||
$condition .= ' OR ';
|
||||
}
|
||||
$condition .= ' (';
|
||||
for ($prevColIndex = 0; $prevColIndex < $colIndex; $prevColIndex++) {
|
||||
$condition .=
|
||||
' `' . $primaryColumn[$prevColIndex] . '` = "' .
|
||||
self::realEscapeString($dbh, $offset[$primaryColumn[$prevColIndex]]) . '" AND ';
|
||||
}
|
||||
$condition .= ' `' . $col . '` > "' . self::realEscapeString($dbh, $offset[$col]) . '")';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$condition = '`' . $primaryColumn . '` > "' . self::realEscapeString($dbh, (is_scalar($offset) ? $offset : 0)) . '"';
|
||||
}
|
||||
|
||||
return (strlen($condition) ? ' AND (' . $condition . ')' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* get current database engine (mysql, maria, percona)
|
||||
*
|
||||
* @param mysqli|resource $dbh database connection
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDBEngine($dbh)
|
||||
{
|
||||
if (($result = self::query($dbh, "SHOW VARIABLES LIKE 'version%'")) === false) {
|
||||
// on query error assume is mysql.
|
||||
return self::DB_ENGINE_MYSQL;
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
while ($row = self::fetchRow($result)) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
self::freeResult($result);
|
||||
|
||||
$version = isset($rows[0][1]) ? $rows[0][1] : false;
|
||||
$versionComment = isset($rows[1][1]) ? $rows[1][1] : false;
|
||||
|
||||
//Default is mysql
|
||||
if ($version === false && $versionComment === false) {
|
||||
return self::DB_ENGINE_MYSQL;
|
||||
}
|
||||
|
||||
if (stripos($version, 'maria') !== false || stripos($versionComment, 'maria') !== false) {
|
||||
return self::DB_ENGINE_MARIA;
|
||||
}
|
||||
|
||||
if (stripos($version, 'percona') !== false || stripos($versionComment, 'percona') !== false) {
|
||||
return self::DB_ENGINE_PERCONA;
|
||||
}
|
||||
|
||||
return self::DB_ENGINE_MYSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape string
|
||||
*
|
||||
* @param resource|mysqli $dbh database connection
|
||||
* @param string $string string to escape
|
||||
*
|
||||
* @return string Returns an escaped string.
|
||||
*/
|
||||
public static function realEscapeString($dbh, $string)
|
||||
{
|
||||
if (self::dbConnType($dbh) === self::CONN_MYSQLI) {
|
||||
return mysqli_real_escape_string($dbh, $string);
|
||||
} else {
|
||||
return mysql_real_escape_string($string, $dbh); // @phpstan-ignore-line
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource|mysqli $dbh database connection
|
||||
* @param string $query query string
|
||||
*
|
||||
* @return mixed <p>Returns <b><code>FALSE</code></b> on failure. For successful <i>SELECT, SHOW, DESCRIBE</i> or
|
||||
* <i>EXPLAIN</i> queries <b>mysqli_query()</b> will return a mysqli_result object.
|
||||
* For other successful queries <b>mysqli_query()</b> will return <b><code>TRUE</code></b>.</p>
|
||||
*/
|
||||
public static function query($dbh, $query)
|
||||
{
|
||||
try {
|
||||
if (self::dbConnType($dbh) === self::CONN_MYSQLI) {
|
||||
return mysqli_query($dbh, $query);
|
||||
} else {
|
||||
return mysql_query($query, $dbh); // @phpstan-ignore-line
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource|mysqli_result $result query result
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function numRows($result)
|
||||
{
|
||||
if (self::dbConnTypeByResult($result) === self::CONN_MYSQLI) {
|
||||
return $result->num_rows;
|
||||
} else {
|
||||
return mysql_num_rows($result); // @phpstan-ignore-line
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource|mysqli_result $result query result
|
||||
*
|
||||
* @return string[]|null|false Returns an array of strings that corresponds to the fetched row. NULL if there are no more rows in result set
|
||||
*/
|
||||
public static function fetchRow($result)
|
||||
{
|
||||
if (self::dbConnTypeByResult($result) === self::CONN_MYSQLI) {
|
||||
return mysqli_fetch_row($result);
|
||||
} elseif (is_resource($result)) {
|
||||
return mysql_fetch_row($result); // @phpstan-ignore-line
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource|mysqli_result $result query result
|
||||
*
|
||||
* @return string[]|null|false Returns an associative array of values representing the fetched row in the result set,
|
||||
* where each key in the array represents the name of one of the result set's
|
||||
* columns or null if there are no more rows in result set.
|
||||
*/
|
||||
public static function fetchAssoc($result)
|
||||
{
|
||||
if (self::dbConnTypeByResult($result) === self::CONN_MYSQLI) {
|
||||
return mysqli_fetch_assoc($result);
|
||||
} elseif (is_resource($result)) {
|
||||
return mysql_fetch_assoc($result); // @phpstan-ignore-line
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource|mysqli_result $result query result
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function freeResult($result)
|
||||
{
|
||||
if (self::dbConnTypeByResult($result) === self::CONN_MYSQLI) {
|
||||
$result->free();
|
||||
return true;
|
||||
} elseif (is_resource($result)) {
|
||||
return mysql_free_result($result); // @phpstan-ignore-line
|
||||
} else {
|
||||
$result = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource|mysqli $dbh database connection
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function error($dbh)
|
||||
{
|
||||
if (self::dbConnType($dbh) === self::CONN_MYSQLI) {
|
||||
if ($dbh instanceof mysqli) {
|
||||
return mysqli_error($dbh);
|
||||
} else {
|
||||
return 'Unable to retrieve the error message from MySQL';
|
||||
}
|
||||
} else {
|
||||
if (is_resource($dbh)) {
|
||||
return mysql_error($dbh); // @phpstan-ignore-line
|
||||
} else {
|
||||
return 'Unable to retrieve the error message from MySQL';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource|mysqli $dbh database connection
|
||||
*
|
||||
* @return string // self::CONN_MYSQLI|self::CONN_MYSQL
|
||||
*/
|
||||
public static function dbConnType($dbh)
|
||||
{
|
||||
return (is_object($dbh) && get_class($dbh) == 'mysqli') ? self::CONN_MYSQLI : self::CONN_MYSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param resource|mysqli_result $result query resyult
|
||||
*
|
||||
* @return string Enum self::CONN_MYSQLI|self::CONN_MYSQL
|
||||
*/
|
||||
public static function dbConnTypeByResult($result)
|
||||
{
|
||||
return (is_object($result) && get_class($result) == 'mysqli_result') ? self::CONN_MYSQLI : self::CONN_MYSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes in input the values of a multiple inster with this format
|
||||
* (v1, v2, v3 ...),(v1, v2, v3, ...),...
|
||||
* and returns a two dimensional array where each item is a row containing the list of values
|
||||
* [
|
||||
* [v1, v2, v3 ...],
|
||||
* [v1, v2, v3 ...],
|
||||
* ...
|
||||
* ]
|
||||
* The return values are not processed but are taken exactly as they are in the dump file.
|
||||
* So if they are escaped it remains unchanged
|
||||
*
|
||||
* @param string $query query values
|
||||
*
|
||||
* @return array<array<scalar>>
|
||||
*/
|
||||
public static function getValuesFromQueryInsert($query)
|
||||
{
|
||||
$result = array();
|
||||
$isItemOpen = false;
|
||||
$isStringOpen = false;
|
||||
$char = '';
|
||||
$pChar = '';
|
||||
|
||||
$currentItem = array();
|
||||
$currentValue = '';
|
||||
|
||||
for ($i = 0; $i < strlen($query); $i++) {
|
||||
$pChar = $char;
|
||||
$char = $query[$i];
|
||||
|
||||
switch ($char) {
|
||||
case '(':
|
||||
if ($isItemOpen == false && !$isStringOpen) {
|
||||
$isItemOpen = true;
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case ')':
|
||||
if ($isItemOpen && !$isStringOpen) {
|
||||
$isItemOpen = false;
|
||||
$currentItem[] = trim($currentValue);
|
||||
$currentValue = '';
|
||||
$result[] = $currentItem;
|
||||
$currentItem = array();
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
case '"':
|
||||
if ($isStringOpen === false && $pChar !== '\\') {
|
||||
$isStringOpen = $char;
|
||||
} elseif ($isStringOpen === $char && $pChar !== '\\') {
|
||||
$isStringOpen = false;
|
||||
}
|
||||
break;
|
||||
case ',':
|
||||
if ($isItemOpen == false) {
|
||||
continue 2;
|
||||
} elseif ($isStringOpen === false) {
|
||||
$currentItem[] = trim($currentValue);
|
||||
$currentValue = '';
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ($isItemOpen == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentValue .= $char;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the inverse of getValuesFromQueryInsert, from an array of values it returns the valody of an insert query
|
||||
*
|
||||
* @param mixed[] $values rows values
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getQueryInsertValuesFromArray(array $values)
|
||||
{
|
||||
|
||||
return implode(
|
||||
',',
|
||||
array_map(
|
||||
function ($rowVals) {
|
||||
return '(' . implode(',', $rowVals) . ')';
|
||||
},
|
||||
$values
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of a value resulting from getValuesFromQueryInsert in string
|
||||
* Then remove the outer quotes and escape
|
||||
* "value\"test" become value"test
|
||||
*
|
||||
* @param string $value value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function parsedQueryValueToString($value)
|
||||
{
|
||||
$result = preg_replace('/^[\'"]?(.*?)[\'"]?$/s', '$1', $value);
|
||||
return stripslashes($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of a value resulting from getValuesFromQueryInsert in int
|
||||
* Then remove the outer quotes and escape
|
||||
* "100" become (int)100
|
||||
*
|
||||
* @param string $value value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function parsedQueryValueToInt($value)
|
||||
{
|
||||
return (int) preg_replace('/^[\'"]?(.*?)[\'"]?$/s', '$1', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of mysqlrealconnect existing flags values from mask
|
||||
*
|
||||
* @see https://www.php.net/manual/en/mysqli.real-connect.php
|
||||
*
|
||||
* @param bool $returnStr if true return define string else values
|
||||
* @param null|int[] $filter if not null only the values that exist and are contained in the array are returned
|
||||
*
|
||||
* @return int[]|string[]
|
||||
*/
|
||||
public static function getMysqlConnectFlagsList($returnStr = true, $filter = null)
|
||||
{
|
||||
static $flagsList = null;
|
||||
|
||||
if (is_null($flagsList)) {
|
||||
$flagsList = array();
|
||||
|
||||
if (defined('MYSQLI_CLIENT_COMPRESS')) {
|
||||
$flagsList[MYSQLI_CLIENT_COMPRESS] = 'MYSQLI_CLIENT_COMPRESS';
|
||||
}
|
||||
if (defined('MYSQLI_CLIENT_FOUND_ROWS')) {
|
||||
$flagsList[MYSQLI_CLIENT_FOUND_ROWS] = 'MYSQLI_CLIENT_FOUND_ROWS';
|
||||
}
|
||||
if (defined('MYSQLI_CLIENT_IGNORE_SPACE')) {
|
||||
$flagsList[MYSQLI_CLIENT_IGNORE_SPACE] = 'MYSQLI_CLIENT_IGNORE_SPACE';
|
||||
}
|
||||
if (defined('MYSQLI_CLIENT_INTERACTIVE')) {
|
||||
$flagsList[MYSQLI_CLIENT_INTERACTIVE] = 'MYSQLI_CLIENT_INTERACTIVE';
|
||||
}
|
||||
if (defined('MYSQLI_CLIENT_SSL')) {
|
||||
$flagsList[MYSQLI_CLIENT_SSL] = 'MYSQLI_CLIENT_SSL';
|
||||
}
|
||||
if (defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT')) {
|
||||
// phpcs:ignore PHPCompatibility.Constants.NewConstants.mysqli_client_ssl_dont_verify_server_certFound
|
||||
$flagsList[MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT] = 'MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT';
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($filter)) {
|
||||
$result = $flagsList;
|
||||
} else {
|
||||
$result = array();
|
||||
foreach ($flagsList as $flagVal => $flag) {
|
||||
if (!in_array($flagVal, $filter)) {
|
||||
continue;
|
||||
}
|
||||
$result[$flagVal] = $flag;
|
||||
}
|
||||
}
|
||||
|
||||
if ($returnStr) {
|
||||
return array_values($result);
|
||||
} else {
|
||||
return array_keys($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of mysqlrealconnect flags values from mask
|
||||
*
|
||||
* @see https://www.php.net/manual/en/mysqli.real-connect.php
|
||||
*
|
||||
* @param int $value mask value
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public static function getMysqlConnectFlagsFromMaskVal($value)
|
||||
{
|
||||
/*
|
||||
MYSQLI_CLIENT_COMPRESS 32
|
||||
MYSQLI_CLIENT_FOUND_ROWS 2
|
||||
MYSQLI_CLIENT_IGNORE_SPACE 256
|
||||
MYSQLI_CLIENT_INTERACTIVE 1024
|
||||
MYSQLI_CLIENT_SSL 2048
|
||||
MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT 64
|
||||
*/
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach (self::getMysqlConnectFlagsList(false) as $flagVal) {
|
||||
if (($value & $flagVal) > 0) {
|
||||
$result[] = $flagVal;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of redundant case insensitive duplicate tables
|
||||
*
|
||||
* @param string $prefix The WP table prefix
|
||||
* @param string[] $duplicates List of case insensitive duplicate table names
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getRedundantDuplicateTables($prefix, $duplicates)
|
||||
{
|
||||
//core tables are not redundant, check with priority
|
||||
foreach (SnapWP::getSiteCoreTables() as $coreTable) {
|
||||
if (($k = array_search($prefix . $coreTable, $duplicates)) !== false) {
|
||||
unset($duplicates[$k]);
|
||||
return array_values($duplicates);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($duplicates as $i => $tableName) {
|
||||
if (stripos($tableName, $prefix) === 0) {
|
||||
//table has prefix, the case sensitive match is not redundant
|
||||
if (strpos($tableName, $prefix) === 0) {
|
||||
unset($duplicates[$i]);
|
||||
break;
|
||||
}
|
||||
|
||||
//no case sensitive match is present, first table is not redundant
|
||||
if ($i === (count($duplicates) - 1)) {
|
||||
unset($duplicates[0]);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
//no prefix present, first table not redundant
|
||||
unset($duplicates[$i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($duplicates);
|
||||
}
|
||||
}
|
||||
1785
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapIO.php
Normal file
1785
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapIO.php
Normal file
File diff suppressed because it is too large
Load Diff
302
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapJson.php
Normal file
302
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapJson.php
Normal file
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
// phpcs:disable
|
||||
require_once(__DIR__ . '/JsonSerializable.php');
|
||||
// phpcs:enable
|
||||
|
||||
class SnapJson
|
||||
{
|
||||
/**
|
||||
* Encode a variable into JSON, with some sanity checks.
|
||||
*
|
||||
* @since 4.1.0
|
||||
*
|
||||
* @param mixed $data Variable (usually an array or object) to encode as JSON.
|
||||
* @param int $options Optional. Options to be passed to json_encode(). Default 0.
|
||||
* @param int $depth Optional. Maximum depth to walk through $data. Must be
|
||||
* greater than 0. Default 512.
|
||||
*
|
||||
* @return string|false The JSON encoded string, or false if it cannot be encoded.
|
||||
*/
|
||||
public static function jsonEncode($data, $options = 0, $depth = 512)
|
||||
{
|
||||
if (function_exists('wp_json_encode')) {
|
||||
return wp_json_encode($data, $options, $depth);
|
||||
}
|
||||
|
||||
/*
|
||||
* json_encode() has had extra params added over the years.
|
||||
* $options was added in 5.3, and $depth in 5.5.
|
||||
* We need to make sure we call it with the correct arguments.
|
||||
*/
|
||||
if (version_compare(PHP_VERSION, '5.5', '>=')) {
|
||||
$args = array($data, $options, $depth);
|
||||
} elseif (version_compare(PHP_VERSION, '5.3', '>=')) {
|
||||
$args = array($data, $options);
|
||||
} else {
|
||||
$args = array($data);
|
||||
}
|
||||
|
||||
$preparedData = self::jsonPrepareData($data);
|
||||
// Prepare the data for JSON serialization.
|
||||
$args[0] = $preparedData;
|
||||
|
||||
$json = @call_user_func_array('json_encode', $args);
|
||||
|
||||
// If json_encode() was successful, no need to do more sanity checking.
|
||||
// ... unless we're in an old version of PHP, and json_encode() returned
|
||||
// a string containing 'null'. Then we need to do more sanity checking.
|
||||
if (false !== $json && ( version_compare(PHP_VERSION, '5.5', '>=') || false === strpos($json, 'null') )) {
|
||||
return $json;
|
||||
}
|
||||
|
||||
try {
|
||||
$args[0] = self::jsonSanityCheck($preparedData, $depth);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return call_user_func_array('json_encode', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_json_encode with pretty print if define exists
|
||||
*
|
||||
* @param mixed $data Variable (usually an array or object) to encode as JSON.
|
||||
* @param int $options Optional. Options to be passed to json_encode(). Default 0.
|
||||
* @param int $depth Optional. Maximum depth to walk through $data. Must be
|
||||
* greater than 0. Default 512.
|
||||
*
|
||||
* @return string|false The JSON encoded string, or false if it cannot be encoded.
|
||||
*/
|
||||
public static function jsonEncodePPrint($data, $options = 0, $depth = 512)
|
||||
{
|
||||
if (defined('JSON_PRETTY_PRINT')) {
|
||||
// phpcs:ignore PHPCompatibility.Constants.NewConstants.json_pretty_printFound
|
||||
return self::jsonEncode($data, JSON_PRETTY_PRINT | $options, $depth);
|
||||
} else {
|
||||
return self::jsonEncode($data, $options, $depth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares response data to be serialized to JSON.
|
||||
*
|
||||
* This supports the JsonSerializable interface for PHP 5.2-5.3 as well.
|
||||
*
|
||||
* @param mixed $data Native representation.
|
||||
*
|
||||
* @return bool|int|float|null|string|mixed[] Data ready for `json_encode()`.
|
||||
*/
|
||||
private static function jsonPrepareData($data)
|
||||
{
|
||||
if (
|
||||
!defined('WP_JSON_SERIALIZE_COMPATIBLE') ||
|
||||
WP_JSON_SERIALIZE_COMPATIBLE === false
|
||||
) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
switch (gettype($data)) {
|
||||
case 'boolean':
|
||||
case 'integer':
|
||||
case 'double':
|
||||
case 'string':
|
||||
case 'NULL':
|
||||
// These values can be passed through.
|
||||
return $data;
|
||||
|
||||
case 'array':
|
||||
// Arrays must be mapped in case they also return objects.
|
||||
return array_map(array(__CLASS__, 'jsonPrepareData'), $data);
|
||||
|
||||
case 'object':
|
||||
// If this is an incomplete object (__PHP_Incomplete_Class), bail.
|
||||
if (!is_object($data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($data instanceof \JsonSerializable) {
|
||||
$data = $data->jsonSerialize();
|
||||
} else {
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
|
||||
// Now, pass the array (or whatever was returned from jsonSerialize through).
|
||||
return self::jsonPrepareData($data);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform sanity checks on data that shall be encoded to JSON.
|
||||
*
|
||||
* @ignore
|
||||
* @since 4.1.0
|
||||
* @access private
|
||||
*
|
||||
* @see wp_json_encode()
|
||||
*
|
||||
* @param mixed $data Variable (usually an array or object) to encode as JSON.
|
||||
* @param int $depth Maximum depth to walk through $data. Must be greater than 0.
|
||||
*
|
||||
* @return mixed The sanitized data that shall be encoded to JSON.
|
||||
*/
|
||||
private static function jsonSanityCheck($data, $depth)
|
||||
{
|
||||
if ($depth < 0) {
|
||||
throw new \Exception('Reached depth limit');
|
||||
}
|
||||
|
||||
if ($data instanceof \JsonSerializable) {
|
||||
$data = $data->jsonSerialize();
|
||||
}
|
||||
|
||||
if (is_array($data)) {
|
||||
$output = array();
|
||||
foreach ($data as $id => $el) {
|
||||
// Don't forget to sanitize the ID!
|
||||
if (is_string($id)) {
|
||||
$clean_id = self::jsonConvertString($id);
|
||||
} else {
|
||||
$clean_id = $id;
|
||||
}
|
||||
|
||||
// Check the element type, so that we're only recursing if we really have to.
|
||||
if (is_array($el) || is_object($el)) {
|
||||
$output[$clean_id] = self::jsonSanityCheck($el, $depth - 1);
|
||||
} elseif (is_string($el)) {
|
||||
$output[$clean_id] = self::jsonConvertString($el);
|
||||
} else {
|
||||
$output[$clean_id] = $el;
|
||||
}
|
||||
}
|
||||
} elseif (is_object($data)) {
|
||||
$output = new \stdClass();
|
||||
foreach ($data as $id => $el) {
|
||||
if (is_string($id)) {
|
||||
$clean_id = self::jsonConvertString($id);
|
||||
} else {
|
||||
$clean_id = $id;
|
||||
}
|
||||
|
||||
if (is_array($el) || is_object($el)) {
|
||||
$output->$clean_id = self::jsonSanityCheck($el, $depth - 1);
|
||||
} elseif (is_string($el)) {
|
||||
$output->$clean_id = self::jsonConvertString($el);
|
||||
} else {
|
||||
$output->$clean_id = $el;
|
||||
}
|
||||
}
|
||||
} elseif (is_string($data)) {
|
||||
return self::jsonConvertString($data);
|
||||
} else {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return json string
|
||||
*
|
||||
* @param string $string data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function jsonConvertString($string)
|
||||
{
|
||||
static $use_mb = null;
|
||||
if (is_null($use_mb)) {
|
||||
$use_mb = function_exists('mb_convert_encoding');
|
||||
}
|
||||
|
||||
if ($use_mb) {
|
||||
$encoding = mb_detect_encoding($string, mb_detect_order(), true);
|
||||
if ($encoding) {
|
||||
return mb_convert_encoding($string, 'UTF-8', $encoding);
|
||||
} else {
|
||||
return mb_convert_encoding($string, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
} else {
|
||||
return self::checkInvalidUTF8($string, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for invalid UTF8 in a string.
|
||||
*
|
||||
* @param string $string The text which is to be checked.
|
||||
* @param bool $strip Optional. Whether to attempt to strip out invalid UTF8. Default is false.
|
||||
*
|
||||
* @return string The checked text.
|
||||
*/
|
||||
public static function checkInvalidUTF8($string, $strip = false)
|
||||
{
|
||||
$string = (string) $string;
|
||||
|
||||
if (0 === strlen($string)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check for support for utf8 in the installed PCRE library once and store the result in a static
|
||||
static $utf8_pcre = null;
|
||||
if (!isset($utf8_pcre)) {
|
||||
$utf8_pcre = @preg_match('/^./u', 'a');
|
||||
}
|
||||
// We can't demand utf8 in the PCRE installation, so just return the string in those cases
|
||||
if (!$utf8_pcre) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
// preg_match fails when it encounters invalid UTF8 in $string
|
||||
if (1 === @preg_match('/^./us', $string)) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
// Attempt to strip the bad chars if requested (not recommended)
|
||||
if ($strip && function_exists('iconv')) {
|
||||
return iconv('utf-8', 'utf-8', $string);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* todo remove esc_attr wp function
|
||||
*
|
||||
* @param mixed $val object to be encoded
|
||||
*
|
||||
* @return string escaped json string
|
||||
*/
|
||||
public static function jsonEncodeEscAttr($val)
|
||||
{
|
||||
return esc_attr(json_encode($val));
|
||||
}
|
||||
|
||||
/**
|
||||
* this function return a json encoded string without quotes at the beginning and the end
|
||||
*
|
||||
* @param string $string json string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getJsonWithoutQuotes($string)
|
||||
{
|
||||
if (!is_string($string)) {
|
||||
throw new \Exception('the function getJsonStringWithoutQuotes take only strings');
|
||||
}
|
||||
|
||||
return substr(self::jsonEncode($string), 1, -1);
|
||||
}
|
||||
}
|
||||
223
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapLog.php
Normal file
223
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapLog.php
Normal file
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
use Error;
|
||||
use Exception;
|
||||
|
||||
class SnapLog
|
||||
{
|
||||
/** @var ?string */
|
||||
public static $logFilepath = null;
|
||||
/** @var ?resource */
|
||||
public static $logHandle = null;
|
||||
|
||||
/**
|
||||
* Init log file
|
||||
*
|
||||
* @param string $logFilepath lof file path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init($logFilepath)
|
||||
{
|
||||
self::$logFilepath = $logFilepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* write in PHP error log with DUP prefix
|
||||
*
|
||||
* @param string $message error message
|
||||
* @param int $type error type
|
||||
*
|
||||
* @return bool true on success or false on failure.
|
||||
*
|
||||
* @link https://php.net/manual/en/function.error-log.php
|
||||
*/
|
||||
public static function phpErr($message, $type = 0)
|
||||
{
|
||||
if (function_exists('error_log')) {
|
||||
return error_log('DUP:' . $message, $type);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove file log if exists
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearLog()
|
||||
{
|
||||
if (file_exists(self::$logFilepath)) {
|
||||
if (self::$logHandle !== null) {
|
||||
fflush(self::$logHandle);
|
||||
fclose(self::$logHandle);
|
||||
self::$logHandle = null;
|
||||
}
|
||||
@unlink(self::$logFilepath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write in log passed object
|
||||
*
|
||||
* @param string $s log string
|
||||
* @param mixed $o object to print
|
||||
* @param boolean $flush if true flush log file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function logObject($s, $o, $flush = false)
|
||||
{
|
||||
self::log($s, $flush);
|
||||
self::log(print_r($o, true), $flush);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write in log file
|
||||
*
|
||||
* @param string $s string to write
|
||||
* @param boolean $flush if true flush log file
|
||||
* @param ?callable $callingFunctionOverride @deprecated 4.0.4 not used
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function log($s, $flush = false, $callingFunctionOverride = null)
|
||||
{
|
||||
// echo "{$s}<br/>";
|
||||
$lfp = self::$logFilepath;
|
||||
// echo "logging $s to {$lfp}<br/>";
|
||||
if (self::$logFilepath === null) {
|
||||
throw new Exception('Logging not initialized');
|
||||
}
|
||||
|
||||
if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
|
||||
$timepart = $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
} else {
|
||||
$timepart = $_SERVER['REQUEST_TIME'];
|
||||
}
|
||||
|
||||
$thread_id = sprintf("%08x", abs(crc32($_SERVER['REMOTE_ADDR'] . $timepart . $_SERVER['REMOTE_PORT'])));
|
||||
|
||||
$s = $thread_id . ' ' . date('h:i:s') . ":$s";
|
||||
|
||||
if (self::$logHandle === null) {
|
||||
self::$logHandle = fopen(self::$logFilepath, 'a');
|
||||
}
|
||||
|
||||
fwrite(self::$logHandle, "$s\n");
|
||||
|
||||
if ($flush) {
|
||||
fflush(self::$logHandle);
|
||||
|
||||
fclose(self::$logHandle);
|
||||
|
||||
self::$logHandle = fopen(self::$logFilepath, 'a');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted string fo value
|
||||
*
|
||||
* @param mixed $var value to convert to string
|
||||
* @param bool $checkCallable if true check if var is callable and display it
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function v2str($var, $checkCallable = false)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backtrace of calling line
|
||||
*
|
||||
* @param string $message message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCurrentbacktrace($message = 'getCurrentLineTrace')
|
||||
{
|
||||
$callers = debug_backtrace();
|
||||
array_shift($callers);
|
||||
$file = $callers[0]['file'];
|
||||
$line = $callers[0]['line'];
|
||||
$result = 'BACKTRACE: ' . $message . "\n";
|
||||
$result .= "\t[" . $file . ':' . $line . "]\n";
|
||||
$result .= self::traceToString($callers, 1, true);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trace string
|
||||
*
|
||||
* @param mixed[] $callers result of debug_backtrace
|
||||
* @param int $fromLevel level to start
|
||||
* @param bool $tab if true apply tab foreach line
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function traceToString($callers, $fromLevel = 0, $tab = false)
|
||||
{
|
||||
$result = '';
|
||||
for ($i = $fromLevel; $i < count($callers); $i++) {
|
||||
$result .= ($tab ? "\t" : '');
|
||||
$trace = $callers[$i];
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exception message file line trace
|
||||
*
|
||||
* @param Exception|Error $e exception object
|
||||
* @param bool $displayMessage if true diplay exception message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTextException($e, $displayMessage = true)
|
||||
{
|
||||
$result = ($displayMessage ? $e->getMessage() . "\n" : '');
|
||||
return $result . "FILE:" . $e->getFile() . '[' . $e->getLIne() . "]\n" .
|
||||
"TRACE:\n" . $e->getTraceAsString();
|
||||
}
|
||||
}
|
||||
43
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapOS.php
Normal file
43
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapOS.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
class SnapOS
|
||||
{
|
||||
const DEFAULT_WINDOWS_MAXPATH = 260;
|
||||
const DEFAULT_LINUX_MAXPATH = 4096;
|
||||
|
||||
/**
|
||||
* Return true if current SO is windows
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isWindows()
|
||||
{
|
||||
static $isWindows = null;
|
||||
if (is_null($isWindows)) {
|
||||
$isWindows = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
|
||||
}
|
||||
return $isWindows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if current SO is OSX
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isOSX()
|
||||
{
|
||||
static $isOSX = null;
|
||||
if (is_null($isOSX)) {
|
||||
$isOSX = (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
|
||||
}
|
||||
return $isOSX;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
/**
|
||||
* Original installer files manager
|
||||
*
|
||||
* This class saves a file or folder in the original files folder and saves the original location persistent.
|
||||
* 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.
|
||||
*/
|
||||
class SnapOrigFileManager
|
||||
{
|
||||
const MODE_MOVE = 'move';
|
||||
const MODE_COPY = 'copy';
|
||||
const ORIG_FOLDER_PREFIX = 'original_files_';
|
||||
const PERSISTANCE_FILE_NAME = 'entries_stored.json';
|
||||
|
||||
/** @var string */
|
||||
protected $persistanceFile = null;
|
||||
/** @var string */
|
||||
protected $origFilesFolder = null;
|
||||
/** @var array<string, array{baseName:string, source:string, stored: string, mode:string, isRelative: bool}> */
|
||||
protected $origFolderEntries = array();
|
||||
/** @var string */
|
||||
protected $rootPath = null;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param string $root wordpress root path
|
||||
* @param string $origFolderParentPath orig files folder path
|
||||
* @param string $hash package hash
|
||||
*/
|
||||
public function __construct($root, $origFolderParentPath, $hash)
|
||||
{
|
||||
$this->rootPath = SnapIO::safePathUntrailingslashit($root, true);
|
||||
$this->origFilesFolder = SnapIO::safePathTrailingslashit($origFolderParentPath, true) . self::ORIG_FOLDER_PREFIX . $hash;
|
||||
$this->persistanceFile = $this->origFilesFolder . '/' . self::PERSISTANCE_FILE_NAME;
|
||||
|
||||
if (file_exists($this->persistanceFile)) {
|
||||
$this->load();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a main folder if don't exist and load the entries
|
||||
*
|
||||
* @param boolean $reset if strue reset orig file folder
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init($reset = false)
|
||||
{
|
||||
$this->createMainFolder($reset);
|
||||
$this->load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create orig file folder
|
||||
*
|
||||
* @param boolean $reset if true delete current folder
|
||||
*
|
||||
* @return boolean return true if succeded
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function createMainFolder($reset = false)
|
||||
{
|
||||
if ($reset) {
|
||||
$this->deleteMainFolder();
|
||||
}
|
||||
|
||||
if (!file_exists($this->origFilesFolder)) {
|
||||
if (!SnapIO::mkdir($this->origFilesFolder, 'u+rwx')) {
|
||||
throw new \Exception('Can\'t create the original files folder ' . SnapLog::v2str($this->origFilesFolder));
|
||||
}
|
||||
}
|
||||
|
||||
$htaccessFile = $this->origFilesFolder . '/.htaccess';
|
||||
if (!file_exists($htaccessFile)) {
|
||||
$content = <<<HTACCESS
|
||||
Order Allow,Deny
|
||||
Deny from All
|
||||
HTACCESS;
|
||||
@file_put_contents($htaccessFile, $content);
|
||||
}
|
||||
|
||||
if (!file_exists($this->persistanceFile)) {
|
||||
$this->save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Main folder path
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getMainFolder()
|
||||
{
|
||||
if (!file_exists($this->origFilesFolder)) {
|
||||
throw new \Exception('Can\'t get the original files folder ' . SnapLog::v2str($this->origFilesFolder));
|
||||
}
|
||||
|
||||
return $this->origFilesFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* delete origianl files folder
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deleteMainFolder()
|
||||
{
|
||||
if (file_exists($this->origFilesFolder) && !SnapIO::rrmdir($this->origFilesFolder)) {
|
||||
throw new \Exception('Can\'t delete the original files folder ' . SnapLog::v2str($this->origFilesFolder));
|
||||
}
|
||||
$this->origFolderEntries = array();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* add a entry on original folder.
|
||||
*
|
||||
* @param string $identifier entry identifier
|
||||
* @param string $path entry path. can be a file or a folder
|
||||
* @param string $mode MODE_MOVE move the item in original folder
|
||||
* MODE_COPY copy the item in original folder
|
||||
* @param bool|string $rename if rename is a string the item is renamed in original folder.
|
||||
*
|
||||
* @return boolean true if succeded
|
||||
*/
|
||||
public function addEntry($identifier, $path, $mode = self::MODE_MOVE, $rename = false)
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$baseName = empty($rename) ? basename($path) : $rename;
|
||||
|
||||
if (($relativePath = SnapIO::getRelativePath($path, $this->rootPath)) === false) {
|
||||
$isRelative = false;
|
||||
} else {
|
||||
$isRelative = true;
|
||||
}
|
||||
$parentFolder = $isRelative ? dirname($relativePath) : SnapIO::removeRootPath(dirname($path));
|
||||
if (empty($parentFolder) || $parentFolder === '.') {
|
||||
$parentFolder = '';
|
||||
} else {
|
||||
$parentFolder .= '/';
|
||||
}
|
||||
$targetFolder = $this->origFilesFolder . '/' . $parentFolder;
|
||||
if (!file_exists($targetFolder)) {
|
||||
SnapIO::mkdirP($targetFolder);
|
||||
}
|
||||
$dest = $targetFolder . $baseName;
|
||||
|
||||
switch ($mode) {
|
||||
case self::MODE_MOVE:
|
||||
// Don't use rename beacause new files must have the current script owner
|
||||
if (!SnapIO::rcopy($path, $dest)) {
|
||||
throw new \Exception('Can\'t copy the original file ' . SnapLog::v2str($path));
|
||||
}
|
||||
if (!SnapIO::rrmdir($path)) {
|
||||
throw new \Exception('Can\'t remove the original file ' . SnapLog::v2str($path));
|
||||
}
|
||||
break;
|
||||
case self::MODE_COPY:
|
||||
if (!SnapIO::rcopy($path, $dest)) {
|
||||
throw new \Exception('Can\'t copy the original file ' . SnapLog::v2str($path));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('invalid mode addEntry');
|
||||
}
|
||||
|
||||
$this->origFolderEntries[$identifier] = array(
|
||||
'baseName' => $baseName,
|
||||
'source' => $isRelative ? $relativePath : $path,
|
||||
'stored' => $parentFolder . $baseName,
|
||||
'mode' => $mode,
|
||||
'isRelative' => $isRelative
|
||||
);
|
||||
|
||||
$this->save();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entry info from identifier
|
||||
*
|
||||
* @param string $identifier orig file identifier
|
||||
*
|
||||
* @return false|array{baseName:string, source:string, stored: string, mode:string, isRelative: bool} false if entry don't exists
|
||||
*/
|
||||
public function getEntry($identifier)
|
||||
{
|
||||
if (isset($this->origFolderEntries[$identifier])) {
|
||||
return $this->origFolderEntries[$identifier];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entry stored path in original folder
|
||||
*
|
||||
* @param string $identifier orig file identifier
|
||||
*
|
||||
* @return boolean|string false if entry don't exists
|
||||
*/
|
||||
public function getEntryStoredPath($identifier)
|
||||
{
|
||||
if (isset($this->origFolderEntries[$identifier])) {
|
||||
return $this->origFilesFolder . '/' . $this->origFolderEntries[$identifier]['stored'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if identifier org file is relative path
|
||||
*
|
||||
* @param string $identifier orig file identifier
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isRelative($identifier)
|
||||
{
|
||||
if (isset($this->origFolderEntries[$identifier])) {
|
||||
return $this->origFolderEntries[$identifier]['isRelative'];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entry target restore path
|
||||
*
|
||||
* @param string $identifier orig file identifier
|
||||
* @param null|string $defaultIfIsAbsolute if isn't null return the value if path is absolute
|
||||
*
|
||||
* @return false|string false if entry don't exists
|
||||
*/
|
||||
public function getEntryTargetPath($identifier, $defaultIfIsAbsolute = null)
|
||||
{
|
||||
if (isset($this->origFolderEntries[$identifier])) {
|
||||
if ($this->origFolderEntries[$identifier]['isRelative']) {
|
||||
return $this->rootPath . '/' . $this->origFolderEntries[$identifier]['source'];
|
||||
} else {
|
||||
if (is_null($defaultIfIsAbsolute)) {
|
||||
return $this->origFolderEntries[$identifier]['source'];
|
||||
} else {
|
||||
return $defaultIfIsAbsolute;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this function restore current entry in original position.
|
||||
* If mode is copy it simply delete the entry else move the entry in original position
|
||||
*
|
||||
* @param string $identifier identified of current entrye
|
||||
* @param boolean $save update saved entries
|
||||
* @param null|string $defaultIfIsAbsolute if isn't null return the value if path is absolute
|
||||
*
|
||||
* @return boolean true if succeded
|
||||
*/
|
||||
public function restoreEntry($identifier, $save = true, $defaultIfIsAbsolute = null)
|
||||
{
|
||||
if (!isset($this->origFolderEntries[$identifier])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stored = $this->getEntryStoredPath($identifier);
|
||||
if (($original = $this->getEntryTargetPath($identifier, $defaultIfIsAbsolute)) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($this->origFolderEntries[$identifier]['mode']) {
|
||||
case self::MODE_MOVE:
|
||||
if (!SnapIO::rename($stored, $original)) {
|
||||
throw new \Exception('Can\'t move the original file ' . SnapLog::v2str($stored));
|
||||
}
|
||||
break;
|
||||
case self::MODE_COPY:
|
||||
if (!SnapIO::rrmdir($stored)) {
|
||||
throw new \Exception('Can\'t delete entry ' . SnapLog::v2str($stored));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('invalid mode addEntry');
|
||||
}
|
||||
|
||||
unset($this->origFolderEntries[$identifier]);
|
||||
if ($save) {
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put all entries on original position and empty original folder
|
||||
*
|
||||
* @param string[] $exclude identifiers list t exclude
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function restoreAll($exclude = array())
|
||||
{
|
||||
foreach (array_keys($this->origFolderEntries) as $ident) {
|
||||
if (in_array($ident, $exclude)) {
|
||||
continue;
|
||||
}
|
||||
$this->restoreEntry($ident, false);
|
||||
}
|
||||
$this->save();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save notices from json file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
if (!file_put_contents($this->persistanceFile, SnapJson::jsonEncodePPrint($this->origFolderEntries))) {
|
||||
throw new \Exception('Can\'t write persistence file');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load notice from json file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function load()
|
||||
{
|
||||
if (file_exists($this->persistanceFile)) {
|
||||
$json = file_get_contents($this->persistanceFile);
|
||||
$this->origFolderEntries = json_decode($json, true);
|
||||
} else {
|
||||
$this->origFolderEntries = array();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
186
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapString.php
Normal file
186
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapString.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
class SnapString
|
||||
{
|
||||
/**
|
||||
* Return true or false in string
|
||||
*
|
||||
* @param mixed $b input value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function boolToString($b)
|
||||
{
|
||||
return ($b ? 'true' : 'false');
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate string and add ellipsis
|
||||
*
|
||||
* @param string $s string to truncate
|
||||
* @param int $maxWidth max length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function truncateString($s, $maxWidth)
|
||||
{
|
||||
if (strlen($s) > $maxWidth) {
|
||||
$s = substr($s, 0, $maxWidth - 3) . '...';
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the $haystack string starts with the $needle
|
||||
*
|
||||
* @param string $haystack The full string to search in
|
||||
* @param string $needle The string to for
|
||||
*
|
||||
* @return bool Returns true if the $haystack string starts with the $needle
|
||||
*/
|
||||
public static function startsWith($haystack, $needle)
|
||||
{
|
||||
return (strpos($haystack, $needle) === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the $haystack string end with the $needle
|
||||
*
|
||||
* @param string $haystack The full string to search in
|
||||
* @param string $needle The string to for
|
||||
*
|
||||
* @return bool Returns true if the $haystack string starts with the $needle
|
||||
*/
|
||||
public static function endsWith($haystack, $needle)
|
||||
{
|
||||
$length = strlen($needle);
|
||||
if ($length == 0) {
|
||||
return true;
|
||||
}
|
||||
return (substr($haystack, -$length) === $needle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the $needle is found in the $haystack
|
||||
*
|
||||
* @param string $haystack The full string to search in
|
||||
* @param string $needle The string to for
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function contains($haystack, $needle)
|
||||
{
|
||||
$pos = strpos($haystack, $needle);
|
||||
return ($pos !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implode array key values to a string
|
||||
*
|
||||
* @param string $glue separator
|
||||
* @param mixed[] $pieces array fo implode
|
||||
* @param string $format format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function implodeKeyVals($glue, $pieces, $format = '%s="%s"')
|
||||
{
|
||||
$strList = array();
|
||||
foreach ($pieces as $key => $value) {
|
||||
if (is_scalar($value)) {
|
||||
$strList[] = sprintf($format, $key, $value);
|
||||
} else {
|
||||
$strList[] = sprintf($format, $key, print_r($value, true));
|
||||
}
|
||||
}
|
||||
return implode($glue, $strList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace last occurrence
|
||||
*
|
||||
* @param string $search The value being searched for
|
||||
* @param string $replace The replacement value that replaces found search values
|
||||
* @param string $str The string or array being searched and replaced on, otherwise known as the haystack
|
||||
* @param boolean $caseSensitive Whether the replacement should be case sensitive or not
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function strLastReplace($search, $replace, $str, $caseSensitive = true)
|
||||
{
|
||||
$pos = $caseSensitive ? strrpos($str, $search) : strripos($str, $search);
|
||||
if (false !== $pos) {
|
||||
$str = substr_replace($str, $replace, $pos, strlen($search));
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed string have html tags
|
||||
*
|
||||
* @param string $string input string
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isHTML($string)
|
||||
{
|
||||
return ($string != strip_tags($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe way to get number of characters
|
||||
*
|
||||
* @param ?string $string input string
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function stringLength($string)
|
||||
{
|
||||
if (!isset($string) || $string == "") { // null == "" is also true
|
||||
return 0;
|
||||
}
|
||||
return strlen($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns case insensitive duplicates
|
||||
*
|
||||
* @param string[] $strings The array of strings to check for duplicates
|
||||
*
|
||||
* @return array<string[]>
|
||||
*/
|
||||
public static function getCaseInsesitiveDuplicates($strings)
|
||||
{
|
||||
$duplicates = array();
|
||||
for ($i = 0; $i < count($strings) - 1; $i++) {
|
||||
$key = strtolower($strings[$i]);
|
||||
|
||||
//already found all instances so don't check again
|
||||
if (isset($duplicates[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($j = $i + 1; $j < count($strings); $j++) {
|
||||
if ($strings[$i] !== $strings[$j] && $key === strtolower($strings[$j])) {
|
||||
$duplicates[$key][] = $strings[$j];
|
||||
}
|
||||
}
|
||||
|
||||
//duplicates were found, add the comparing string to list
|
||||
if (isset($duplicates[$key])) {
|
||||
$duplicates[$key][] = $strings[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return $duplicates;
|
||||
}
|
||||
}
|
||||
237
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapURL.php
Normal file
237
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapURL.php
Normal file
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\Snap;
|
||||
|
||||
class SnapURL
|
||||
{
|
||||
/** @var array<string, scalar> */
|
||||
protected static $DEF_ARRAY_PARSE_URL = array(
|
||||
'scheme' => false,
|
||||
'host' => false,
|
||||
'port' => false,
|
||||
'user' => false,
|
||||
'pass' => false,
|
||||
'path' => '',
|
||||
'query' => false,
|
||||
'fragment' => false
|
||||
);
|
||||
|
||||
/**
|
||||
* Append a new query value to the end of a URL
|
||||
*
|
||||
* @param string $url The URL to append the new value to
|
||||
* @param string $key The new key name
|
||||
* @param ?scalar $value The new key name value
|
||||
*
|
||||
* @return string Returns the new URL with with the query string name and value
|
||||
*/
|
||||
public static function appendQueryValue($url, $key, $value)
|
||||
{
|
||||
$separator = (parse_url($url, PHP_URL_QUERY) == null) ? '?' : '&';
|
||||
$modified_url = $url . "$separator$key=" . $value;
|
||||
|
||||
return $modified_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add www. in url if don't have
|
||||
*
|
||||
* @param string $url input URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function wwwAdd($url)
|
||||
{
|
||||
return preg_replace('/^((?:\w+\:)?\/\/)?(?!www\.)(.+)/', '$1www.$2', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove www. in url if don't have
|
||||
*
|
||||
* @param string $url input URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function wwwRemove($url)
|
||||
{
|
||||
return preg_replace('/^((?:\w+\:)?\/\/)?www\.(.+)/', '$1$2', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches current URL via PHP
|
||||
*
|
||||
* @param bool $queryString If true the query string will also be returned.
|
||||
* @param boolean $requestUri If true check REQUEST_URI else SCRIPT_NAME
|
||||
* @param int $getParentDirLevel If 0 get current script name or parent folder, if 1 parent folder if 2 parent of parent folder ...
|
||||
*
|
||||
* @return string The current page url
|
||||
*/
|
||||
public static function getCurrentUrl($queryString = true, $requestUri = false, $getParentDirLevel = 0)
|
||||
{
|
||||
// *** HOST
|
||||
if (isset($_SERVER['HTTP_X_ORIGINAL_HOST'])) {
|
||||
$host = $_SERVER['HTTP_X_ORIGINAL_HOST'];
|
||||
} else {
|
||||
$host = isset($_SERVER['HTTP_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current query string data array
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getCurrentQueryURLdata()
|
||||
{
|
||||
$result = array();
|
||||
if (!isset($_SERVER['QUERY_STRING']) || strlen($_SERVER['QUERY_STRING']) == 0) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
parse_str($_SERVER['QUERY_STRING'], $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* this function is a native PHP parse_url wrapper
|
||||
* this function returns an associative array with all the keys present and the values = false if they do not exist.
|
||||
*
|
||||
* @param string $url <p>The URL to parse. Invalid characters are replaced by <i>_</i>.</p>
|
||||
* @param int $component if != 1 return specific URL component
|
||||
*
|
||||
* @return mixed[]|string|int|null|false <p>On seriously malformed URLs, <b>parse_url()</b> may return <b><code>FALSE</code></b>.</p>
|
||||
* <p>If the <code>component</code> parameter is omitted, an associative <code>array</code> is returned.
|
||||
* At least one element will be present within the array. Potential keys within this array are:</p>
|
||||
* <ul>
|
||||
* <li> scheme - e.g. http </li>
|
||||
* <li> host </li>
|
||||
* <li> port </li>
|
||||
* <li> user </li>
|
||||
* <li> pass </li>
|
||||
* <li> path </li>
|
||||
* <li> query - after the question mark <i>?</i> </li>
|
||||
* <li> fragment - after the hashmark <i>#</i> </li>
|
||||
* </ul>
|
||||
* <p>If the <code>component</code> parameter is specified,
|
||||
* <b>parse_url()</b> returns a <code>string</code> (or an <code>integer</code>,
|
||||
* in the case of <b><code>PHP_URL_PORT</code></b>) instead of an <code>array</code>.
|
||||
* If the requested component doesn't exist within the given URL, <b><code>NULL</code></b> will be returned.</p>
|
||||
*/
|
||||
public static function parseUrl($url, $component = -1)
|
||||
{
|
||||
if (preg_match('/^([a-zA-Z0-9]+\:)?\/\//', $url) !== 1) {
|
||||
// fix invalid URL for only host string ex. 'myhost.com'
|
||||
$url = '//' . $url;
|
||||
}
|
||||
|
||||
$result = parse_url($url, $component);
|
||||
if (is_array($result)) {
|
||||
$result = array_merge(self::$DEF_ARRAY_PARSE_URL, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove scheme from URL
|
||||
*
|
||||
* @param string $url source url
|
||||
* @param bool $removeWww if true remove www
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function removeScheme($url, $removeWww = false)
|
||||
{
|
||||
$parts = self::parseUrl($url);
|
||||
unset($parts['scheme']);
|
||||
$result = self::buildUrl($parts);
|
||||
if ($removeWww) {
|
||||
$result = self::wwwRemove($result);
|
||||
}
|
||||
return ltrim($result, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* this function build a url from array result of parse url.
|
||||
* if work with both parse_url native function result and snap parseUrl result
|
||||
*
|
||||
* @param array<string, mixed> $parts url parts from parseUrl
|
||||
*
|
||||
* @return bool|string return false if param isn't array
|
||||
*/
|
||||
public static function buildUrl($parts)
|
||||
{
|
||||
if (!is_array($parts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = '';
|
||||
$result .= (isset($parts['scheme']) && $parts['scheme'] !== false) ? $parts['scheme'] . ':' : '';
|
||||
$result .= (
|
||||
(isset($parts['user']) && $parts['user'] !== false) ||
|
||||
(isset($parts['host']) && $parts['host'] !== false)) ? '//' : '';
|
||||
|
||||
$result .= (isset($parts['user']) && $parts['user'] !== false) ? $parts['user'] : '';
|
||||
$result .= (isset($parts['pass']) && $parts['pass'] !== false) ? ':' . $parts['pass'] : '';
|
||||
$result .= (isset($parts['user']) && $parts['user'] !== false) ? '@' : '';
|
||||
|
||||
$result .= (isset($parts['host']) && $parts['host'] !== false) ? $parts['host'] : '';
|
||||
$result .= (isset($parts['port']) && $parts['port'] !== false) ? ':' . $parts['port'] : '';
|
||||
|
||||
$result .= (isset($parts['path']) && $parts['path'] !== false) ? $parts['path'] : '';
|
||||
$result .= (isset($parts['query']) && $parts['query'] !== false) ? '?' . $parts['query'] : '';
|
||||
$result .= (isset($parts['fragment']) && $parts['fragment'] !== false) ? '#' . $parts['fragment'] : '';
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode alla chars
|
||||
*
|
||||
* @param string $url input URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function urlEncodeAll($url)
|
||||
{
|
||||
$hex = unpack('H*', urldecode($url));
|
||||
return preg_replace('~..~', '%$0', strtoupper($hex[1]));
|
||||
}
|
||||
}
|
||||
1003
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapUtil.php
Normal file
1003
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapUtil.php
Normal file
File diff suppressed because it is too large
Load Diff
1052
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapWP.php
Normal file
1052
html/wp-content/plugins/duplicator/src/Libs/Snap/SnapWP.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
//silent
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,496 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\WpConfig;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Transforms a wp-config.php file.
|
||||
* Fork of wp-cli/trnasformer
|
||||
*/
|
||||
class WPConfigTransformer
|
||||
{
|
||||
const REPLACE_TEMP_STIRNG = '_1_2_RePlAcE_3_4_TeMp_5_6_StRiNg_7_8_';
|
||||
|
||||
/**
|
||||
* Path to the wp-config.php file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $wp_config_path;
|
||||
|
||||
/**
|
||||
* Original source of the wp-config.php file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $wp_config_src;
|
||||
|
||||
/**
|
||||
* Array of parsed configs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $wp_configs = array();
|
||||
|
||||
/**
|
||||
* Instantiates the class with a valid wp-config.php.
|
||||
*
|
||||
* @throws Exception If the wp-config.php file is missing.
|
||||
* @throws Exception If the wp-config.php file is not writable.
|
||||
*
|
||||
* @param string $wp_config_path Path to a wp-config.php file.
|
||||
*/
|
||||
public function __construct($wp_config_path)
|
||||
{
|
||||
if (! file_exists($wp_config_path)) {
|
||||
throw new Exception('wp-config.php file does not exist. Path:' . $wp_config_path);
|
||||
}
|
||||
// Duplicator Extra
|
||||
/*
|
||||
if ( ! is_writable( $wp_config_path ) ) {
|
||||
throw new Exception( 'wp-config.php file is not writable.' );
|
||||
}
|
||||
*/
|
||||
|
||||
$this->wp_config_path = $wp_config_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a config exists in the wp-config.php file.
|
||||
*
|
||||
* @throws Exception If the wp-config.php file is empty.
|
||||
* @throws Exception If the requested config type is invalid.
|
||||
*
|
||||
* @param string $type Config type (constant or variable).
|
||||
* @param string $name Config name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($type, $name)
|
||||
{
|
||||
$wp_config_src = file_get_contents($this->wp_config_path);
|
||||
|
||||
if (! trim($wp_config_src)) {
|
||||
throw new Exception('wp-config.php file is empty.');
|
||||
}
|
||||
|
||||
// SnapCreek custom change
|
||||
// Normalize the newline to prevent an issue coming from OSX
|
||||
$wp_config_src = str_replace(array("\r\n", "\r"), "\n", $wp_config_src);
|
||||
|
||||
$this->wp_config_src = $wp_config_src;
|
||||
$this->wp_configs = $this->parseWpConfig($this->wp_config_src);
|
||||
|
||||
if (! isset($this->wp_configs[ $type ])) {
|
||||
throw new Exception("Config type '{$type}' does not exist.");
|
||||
}
|
||||
|
||||
return isset($this->wp_configs[ $type ][ $name ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a config in the wp-config.php file.
|
||||
*
|
||||
* @throws Exception If the wp-config.php file is empty.
|
||||
* @throws Exception If the requested config type is invalid.
|
||||
*
|
||||
* @param string $type Config type (constant or variable).
|
||||
* @param string $name Config name.
|
||||
* @param bool $get_real_value if true return real value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValue($type, $name, $get_real_value = true)
|
||||
{
|
||||
$wp_config_src = file_get_contents($this->wp_config_path);
|
||||
if (! trim($wp_config_src)) {
|
||||
throw new Exception('wp-config.php file is empty.');
|
||||
}
|
||||
|
||||
// SnapCreek custom change
|
||||
// Normalize the newline to prevent an issue coming from OSX
|
||||
$wp_config_src = str_replace(array("\r\n", "\r"), "\n", $wp_config_src);
|
||||
|
||||
$this->wp_config_src = $wp_config_src;
|
||||
$this->wp_configs = $this->parseWpConfig($this->wp_config_src);
|
||||
|
||||
if (! isset($this->wp_configs[ $type ])) {
|
||||
throw new Exception("Config type '{$type}' does not exist.");
|
||||
}
|
||||
|
||||
// Duplicator Extra
|
||||
$val = $this->wp_configs[ $type ][ $name ]['value'];
|
||||
if ($get_real_value) {
|
||||
return self::getRealValFromVal($val);
|
||||
} else {
|
||||
return $val;
|
||||
}
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get typed val from string val
|
||||
*
|
||||
* @param string $val string value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getRealValFromVal($val)
|
||||
{
|
||||
if ($val[0] === '\'') {
|
||||
// string with '
|
||||
$result = substr($val, 1, strlen($val) - 2);
|
||||
return str_replace(array('\\\'', '\\\\'), array('\'', '\\'), $result);
|
||||
} elseif ($val[0] === '"') {
|
||||
// string with "
|
||||
return json_decode(str_replace('\\$', '$', $val));
|
||||
} elseif (strcasecmp($val, 'true') === 0) {
|
||||
return true;
|
||||
} elseif (strcasecmp($val, 'false') === 0) {
|
||||
return false;
|
||||
} elseif (strcasecmp($val, 'null') === 0) {
|
||||
return null;
|
||||
} elseif (preg_match('/^[-+]?[0-9]+$/', $val)) {
|
||||
return (int) $val;
|
||||
} elseif (preg_match('/^[-+]?[0-9]+\.[0-9]+$/', $val)) {
|
||||
return (float) $val;
|
||||
} else {
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a config to the wp-config.php file.
|
||||
*
|
||||
* @throws Exception If the config value provided is not a string.
|
||||
* @throws Exception If the config placement anchor could not be located.
|
||||
*
|
||||
* @param string $type Config type (constant or variable).
|
||||
* @param string $name Config name.
|
||||
* @param string $value Config value.
|
||||
* @param array $options (optional) Array of special behavior options.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function add($type, $name, $value, array $options = array())
|
||||
{
|
||||
if (! is_string($value)) {
|
||||
throw new Exception('Config value must be a string.');
|
||||
}
|
||||
|
||||
if ($this->exists($type, $name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'raw' => false, // Display value in raw format without quotes.
|
||||
'anchor' => "/* That's all, stop editing!", // Config placement anchor string.
|
||||
'separator' => PHP_EOL, // Separator between config definition and anchor string.
|
||||
'placement' => 'before', // Config placement direction (insert before or after).
|
||||
);
|
||||
|
||||
list( $raw, $anchor, $separator, $placement ) = array_values(array_merge($defaults, $options));
|
||||
|
||||
$raw = (bool) $raw;
|
||||
$anchor = (string) $anchor;
|
||||
$separator = (string) $separator;
|
||||
$placement = (string) $placement;
|
||||
|
||||
// Custom code by the SnapCreek Team
|
||||
if (false === strpos($this->wp_config_src, $anchor)) {
|
||||
$other_anchor_points = array(
|
||||
'/** Absolute path to the WordPress directory',
|
||||
// ABSPATH defined check with single quote
|
||||
"if ( !defined('ABSPATH') )",
|
||||
"if ( ! defined( 'ABSPATH' ) )",
|
||||
"if (!defined('ABSPATH') )",
|
||||
"if(!defined('ABSPATH') )",
|
||||
"if(!defined('ABSPATH'))",
|
||||
"if ( ! defined( 'ABSPATH' ))",
|
||||
"if ( ! defined( 'ABSPATH') )",
|
||||
"if ( ! defined('ABSPATH' ) )",
|
||||
"if (! defined( 'ABSPATH' ))",
|
||||
"if (! defined( 'ABSPATH') )",
|
||||
"if (! defined('ABSPATH' ) )",
|
||||
"if ( !defined( 'ABSPATH' ))",
|
||||
"if ( !defined( 'ABSPATH') )",
|
||||
"if ( !defined('ABSPATH' ) )",
|
||||
"if( !defined( 'ABSPATH' ))",
|
||||
"if( !defined( 'ABSPATH') )",
|
||||
"if( !defined('ABSPATH' ) )",
|
||||
// ABSPATH defined check with double quote
|
||||
'if ( !defined("ABSPATH") )',
|
||||
'if ( ! defined( "ABSPATH" ) )',
|
||||
'if (!defined("ABSPATH") )',
|
||||
'if(!defined("ABSPATH") )',
|
||||
'if(!defined("ABSPATH"))',
|
||||
'if ( ! defined( "ABSPATH" ))',
|
||||
'if ( ! defined( "ABSPATH") )',
|
||||
'if ( ! defined("ABSPATH" ) )',
|
||||
'if (! defined( "ABSPATH" ))',
|
||||
'if (! defined( "ABSPATH") )',
|
||||
'if (! defined("ABSPATH" ) )',
|
||||
'if ( !defined( "ABSPATH" ))',
|
||||
'if ( !defined( "ABSPATH") )',
|
||||
'if ( !defined("ABSPATH" ) )',
|
||||
'if( !defined( "ABSPATH" ))',
|
||||
'if( !defined( "ABSPATH") )',
|
||||
'if( !defined("ABSPATH" ) )',
|
||||
|
||||
'/** Sets up WordPress vars and included files',
|
||||
'require_once(ABSPATH',
|
||||
'require_once ABSPATH',
|
||||
'require_once( ABSPATH',
|
||||
'require_once',
|
||||
"define( 'DB_NAME'",
|
||||
'define( "DB_NAME"',
|
||||
"define('DB_NAME'",
|
||||
'define("DB_NAME"',
|
||||
'require',
|
||||
'include_once',
|
||||
);
|
||||
foreach ($other_anchor_points as $anchor_point) {
|
||||
$anchor_point = (string) $anchor_point;
|
||||
if (false !== strpos($this->wp_config_src, $anchor_point)) {
|
||||
$anchor = $anchor_point;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (false === strpos($this->wp_config_src, $anchor)) {
|
||||
throw new Exception('Unable to locate placement anchor.');
|
||||
}
|
||||
|
||||
$new_src = $this->normalize($type, $name, $this->formatValue($value, $raw));
|
||||
$new_src = ( 'after' === $placement ) ? $anchor . $separator . $new_src : $new_src . $separator . $anchor;
|
||||
$contents = str_replace($anchor, $new_src, $this->wp_config_src);
|
||||
|
||||
return $this->save($contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing config in the wp-config.php file.
|
||||
*
|
||||
* @throws Exception If the config value provided is not a string.
|
||||
*
|
||||
* @param string $type Config type (constant or variable).
|
||||
* @param string $name Config name.
|
||||
* @param string $value Config value.
|
||||
* @param array $options (optional) Array of special behavior options.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update($type, $name, $value, array $options = array())
|
||||
{
|
||||
if (! is_string($value)) {
|
||||
throw new Exception('Config value must be a string.');
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'add' => true, // Add the config if missing.
|
||||
'raw' => false, // Display value in raw format without quotes.
|
||||
'normalize' => false, // Normalize config output using WP Coding Standards.
|
||||
);
|
||||
|
||||
list( $add, $raw, $normalize ) = array_values(array_merge($defaults, $options));
|
||||
|
||||
$add = (bool) $add;
|
||||
$raw = (bool) $raw;
|
||||
$normalize = (bool) $normalize;
|
||||
|
||||
if (! $this->exists($type, $name)) {
|
||||
return ( $add ) ? $this->add($type, $name, $value, $options) : false;
|
||||
}
|
||||
|
||||
$old_src = $this->wp_configs[ $type ][ $name ]['src'];
|
||||
$old_value = $this->wp_configs[ $type ][ $name ]['value'];
|
||||
$new_value = $this->formatValue($value, $raw);
|
||||
|
||||
if ($normalize) {
|
||||
$new_src = $this->normalize($type, $name, $new_value);
|
||||
} else {
|
||||
$new_parts = $this->wp_configs[ $type ][ $name ]['parts'];
|
||||
$new_parts[1] = str_replace($old_value, $new_value, $new_parts[1]); // Only edit the value part.
|
||||
$new_src = implode('', $new_parts);
|
||||
}
|
||||
|
||||
$contents = preg_replace(
|
||||
sprintf('/(?<=^|;|<\?php\s|<\?\s)(\s*?)%s/m', preg_quote(trim($old_src), '/')),
|
||||
'$1' . self::REPLACE_TEMP_STIRNG,
|
||||
$this->wp_config_src
|
||||
);
|
||||
$contents = str_replace(self::REPLACE_TEMP_STIRNG, trim($new_src), $contents);
|
||||
return $this->save($contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a config from the wp-config.php file.
|
||||
*
|
||||
* @param string $type Config type (constant or variable).
|
||||
* @param string $name Config name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($type, $name)
|
||||
{
|
||||
if (! $this->exists($type, $name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pattern = sprintf('/(?<=^|;|<\?php\s|<\?\s)%s\s*(\S|$)/m', preg_quote($this->wp_configs[ $type ][ $name ]['src'], '/'));
|
||||
$contents = preg_replace($pattern, '$1', $this->wp_config_src);
|
||||
|
||||
return $this->save($contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies formatting to a config value.
|
||||
*
|
||||
* @throws Exception When a raw value is requested for an empty string.
|
||||
*
|
||||
* @param string $value Config value.
|
||||
* @param bool $raw Display value in raw format without quotes.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function formatValue($value, $raw)
|
||||
{
|
||||
if ($raw && '' === trim($value)) {
|
||||
throw new Exception('Raw value for empty string not supported.');
|
||||
}
|
||||
|
||||
return ( $raw ) ? $value : var_export($value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the source output for a name/value pair.
|
||||
*
|
||||
* @throws Exception If the requested config type does not support normalization.
|
||||
*
|
||||
* @param string $type Config type (constant or variable).
|
||||
* @param string $name Config name.
|
||||
* @param mixed $value Config value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalize($type, $name, $value)
|
||||
{
|
||||
if ('constant' === $type) {
|
||||
$placeholder = "define( '%s', %s );";
|
||||
} elseif ('variable' === $type) {
|
||||
$placeholder = '$%s = %s;';
|
||||
} else {
|
||||
throw new Exception("Unable to normalize config type '{$type}'.");
|
||||
}
|
||||
|
||||
return sprintf($placeholder, $name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the source of a wp-config.php file.
|
||||
*
|
||||
* @param string $src Config file source.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseWpConfig($src)
|
||||
{
|
||||
$configs = array();
|
||||
$configs['constant'] = array();
|
||||
$configs['variable'] = array();
|
||||
|
||||
if (function_exists('token_get_all')) {
|
||||
// Strip comments.
|
||||
foreach (token_get_all($src) as $token) {
|
||||
if (in_array($token[0], array( T_COMMENT, T_DOC_COMMENT ), true)) {
|
||||
$src = str_replace($token[1], '', $src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preg_match_all(
|
||||
'/(?<=^|;|<\?php\s|<\?\s)' .
|
||||
'(\h*define\s*\(\s*[\'"](\w*?)[\'"]\s*)(,\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*)' .
|
||||
'((?:,\s*(?:true|false)\s*)?\)\s*;)/ims',
|
||||
$src,
|
||||
$constants
|
||||
);
|
||||
preg_match_all('/(?<=^|;|<\?php\s|<\?\s)(\h*\$(\w+)\s*=)(\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*;)/ims', $src, $variables);
|
||||
|
||||
if (
|
||||
!empty($constants[0]) &&
|
||||
!empty($constants[1]) &&
|
||||
!empty($constants[2]) &&
|
||||
!empty($constants[3]) &&
|
||||
!empty($constants[4]) &&
|
||||
!empty($constants[5])
|
||||
) {
|
||||
foreach ($constants[2] as $index => $name) {
|
||||
$configs['constant'][ $name ] = array(
|
||||
'src' => $constants[0][ $index ],
|
||||
'value' => $constants[4][ $index ],
|
||||
'parts' => array(
|
||||
$constants[1][ $index ],
|
||||
$constants[3][ $index ],
|
||||
$constants[5][ $index ],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($variables[0]) && ! empty($variables[1]) && ! empty($variables[2]) && ! empty($variables[3]) && ! empty($variables[4])) {
|
||||
// Remove duplicate(s), last definition wins.
|
||||
$variables[2] = array_reverse(array_unique(array_reverse($variables[2], true)), true);
|
||||
foreach ($variables[2] as $index => $name) {
|
||||
$configs['variable'][ $name ] = array(
|
||||
'src' => $variables[0][ $index ],
|
||||
'value' => $variables[4][ $index ],
|
||||
'parts' => array(
|
||||
$variables[1][ $index ],
|
||||
$variables[3][ $index ],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves new contents to the wp-config.php file.
|
||||
*
|
||||
* @throws Exception If the config file content provided is empty.
|
||||
* @throws Exception If there is a failure when saving the wp-config.php file.
|
||||
*
|
||||
* @param string $contents New config contents.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function save($contents)
|
||||
{
|
||||
if (!trim($contents)) {
|
||||
throw new Exception('Cannot save the wp-config.php file with empty contents.');
|
||||
}
|
||||
|
||||
if ($contents === $this->wp_config_src) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = file_put_contents($this->wp_config_path, $contents, LOCK_EX);
|
||||
|
||||
if (false === $result) {
|
||||
throw new Exception('Failed to update the wp-config.php file.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Libs\WpConfig;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Transforms a wp-config.php file.
|
||||
*/
|
||||
class WPConfigTransformerSrc extends WPConfigTransformer
|
||||
{
|
||||
/**
|
||||
* Instantiates the class with a valid wp-config.php scr text
|
||||
*
|
||||
* @param string $wp_config_src Path to a wp-config.php file.
|
||||
*/
|
||||
public function __construct($wp_config_src)
|
||||
{
|
||||
// Normalize the newline to prevent an issue coming from OSX
|
||||
$this->wp_config_src = str_replace(array("\n\r", "\r"), array("\n", "\n"), $wp_config_src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSrc()
|
||||
{
|
||||
return $this->wp_config_src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a config exists in the wp-config.php src
|
||||
*
|
||||
* @throws Exception If the wp-config.php file is empty.
|
||||
* @throws Exception If the requested config type is invalid.
|
||||
*
|
||||
* @param string $type Config type (constant or variable).
|
||||
* @param string $name Config name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($type, $name)
|
||||
{
|
||||
$this->wp_configs = $this->parseWpConfig($this->wp_config_src);
|
||||
|
||||
if (!isset($this->wp_configs[$type])) {
|
||||
throw new Exception("Config type '{$type}' does not exist.");
|
||||
}
|
||||
|
||||
return isset($this->wp_configs[$type][$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a config in the wp-config.php src
|
||||
*
|
||||
* @param string $type Config type (constant or variable).
|
||||
* @param string $name Config name.
|
||||
* @param bool $get_real_value if true return typed value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValue($type, $name, $get_real_value = true)
|
||||
{
|
||||
$this->wp_configs = $this->parseWpConfig($this->wp_config_src);
|
||||
|
||||
if (!isset($this->wp_configs[$type])) {
|
||||
throw new Exception("Config type '{$type}' does not exist.");
|
||||
}
|
||||
|
||||
// Duplicator Extra
|
||||
$val = $this->wp_configs[$type][$name]['value'];
|
||||
if ($get_real_value) {
|
||||
return self::getRealValFromVal($val);
|
||||
} else {
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update wp_config_src
|
||||
*
|
||||
* @param string $contents config content
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function save($contents)
|
||||
{
|
||||
$this->wp_config_src = $contents;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
151
html/wp-content/plugins/duplicator/src/Lite/Requirements.php
Normal file
151
html/wp-content/plugins/duplicator/src/Lite/Requirements.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class that collects the functions of initial checks on the requirements to run the plugin
|
||||
*
|
||||
* Standard: PSR-2
|
||||
*
|
||||
* @link http://www.php-fig.org/psr/psr-2
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Lite;
|
||||
|
||||
class Requirements
|
||||
{
|
||||
const DUP_PRO_PLUGIN_KEY = 'duplicator-pro/duplicator-pro.php';
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string // current plugin file full path
|
||||
*/
|
||||
protected static $pluginFile = '';
|
||||
|
||||
/**
|
||||
*
|
||||
* @var string // message on deactivation
|
||||
*/
|
||||
protected static $deactivationMessage = '';
|
||||
|
||||
/**
|
||||
* This function checks the requirements to run Duplicator.
|
||||
* At this point WordPress is not yet completely initialized so functionality is limited.
|
||||
* It need to hook into "admin_init" to get the full functionality of WordPress.
|
||||
*
|
||||
* @param string $pluginFile // main plugin file path
|
||||
*
|
||||
* @return boolean // true if plugin can be executed
|
||||
*/
|
||||
public static function canRun($pluginFile)
|
||||
{
|
||||
$result = true;
|
||||
self::$pluginFile = $pluginFile;
|
||||
|
||||
if ($result === true && self::isPluginActive(self::DUP_PRO_PLUGIN_KEY)) {
|
||||
add_action('admin_init', array(__CLASS__, 'addProEnableNotice'));
|
||||
$pluginUrl = (is_multisite() ? network_admin_url('plugins.php') : admin_url('plugins.php'));
|
||||
self::$deactivationMessage = sprintf(
|
||||
esc_html_x(
|
||||
'Can\'t enable Duplicator LITE if the PRO version is enabled. Please deactivate Duplicator PRO,
|
||||
then reactivate LITE version from the %1$splugins page%2$s.',
|
||||
'%1$s and %2$s are <a> tags',
|
||||
'duplicator'
|
||||
),
|
||||
'<a href="' . esc_url($pluginUrl) . '">',
|
||||
'</a>'
|
||||
);
|
||||
$result = false;
|
||||
}
|
||||
|
||||
if ($result === false) {
|
||||
register_activation_hook($pluginFile, array(__CLASS__, 'deactivateOnActivation'));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $plugin plugin slug
|
||||
*
|
||||
* @return boolean return true if plugin key is active and plugin file exists
|
||||
*/
|
||||
protected static function isPluginActive($plugin)
|
||||
{
|
||||
$isActive = false;
|
||||
if (in_array($plugin, (array) get_option('active_plugins', array()))) {
|
||||
$isActive = true;
|
||||
}
|
||||
|
||||
if (is_multisite()) {
|
||||
$plugins = get_site_option('active_sitewide_plugins');
|
||||
if (isset($plugins[$plugin])) {
|
||||
$isActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ($isActive && file_exists(WP_PLUGIN_DIR . '/' . $plugin));
|
||||
}
|
||||
|
||||
/**
|
||||
* display admin notice only if user can manage plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addProEnableNotice()
|
||||
{
|
||||
if (current_user_can('activate_plugins')) {
|
||||
add_action('admin_notices', array(__CLASS__, 'proEnabledNotice'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* display admin notice
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addMultisiteNotice()
|
||||
{
|
||||
if (current_user_can('activate_plugins')) {
|
||||
add_action('admin_notices', array(__CLASS__, 'multisiteNotice'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* deactivate current plugin on activation
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function deactivateOnActivation()
|
||||
{
|
||||
deactivate_plugins(plugin_basename(self::$pluginFile));
|
||||
wp_die(self::$deactivationMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display admin notice if duplicator pro is enabled
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function proEnabledNotice()
|
||||
{
|
||||
$pluginUrl = (is_multisite() ? network_admin_url('plugins.php') : admin_url('plugins.php'));
|
||||
?>
|
||||
<div class="error notice">
|
||||
<p>
|
||||
<span class="dashicons dashicons-warning"></span>
|
||||
<b><?php _e('Duplicator Notice:', 'duplicator'); ?></b>
|
||||
<?php _e('The "Duplicator Lite" and "Duplicator Pro" plugins cannot both be active at the same time. ', 'duplicator'); ?>
|
||||
</p>
|
||||
<p>
|
||||
<?php _e('To use "Duplicator LITE" please deactivate "Duplicator PRO" from the ', 'duplicator'); ?>
|
||||
<a href="<?php echo esc_url($pluginUrl); ?>">
|
||||
<?php _e('plugins page', 'duplicator'); ?>.
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
128
html/wp-content/plugins/duplicator/src/Utils/Autoloader.php
Normal file
128
html/wp-content/plugins/duplicator/src/Utils/Autoloader.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Auloader calsses
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2021, Snapcreek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Utils;
|
||||
|
||||
/**
|
||||
* Autoloader calss, dont user Duplicator library here
|
||||
*/
|
||||
final class Autoloader
|
||||
{
|
||||
const ROOT_NAMESPACE = 'Duplicator\\';
|
||||
const ROOT_INSTALLER_NAMESPACE = 'Duplicator\\Installer\\';
|
||||
|
||||
protected static $nameSpacesMapping = null;
|
||||
|
||||
/**
|
||||
* Register autoloader function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
spl_autoload_register(array(__CLASS__, 'load'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load class
|
||||
*
|
||||
* @param string $className class name
|
||||
*
|
||||
* @return bool return true if class is loaded
|
||||
*/
|
||||
public static function load($className)
|
||||
{
|
||||
// @todo remove legacy logic in autoloading when duplicator is fully converted.
|
||||
if (strpos($className, self::ROOT_NAMESPACE) !== 0) {
|
||||
$legacyMappging = self::customLegacyMapping();
|
||||
$legacyClass = strtolower(ltrim($className, '\\'));
|
||||
if (array_key_exists($legacyClass, $legacyMappging)) {
|
||||
if (file_exists($legacyMappging[$legacyClass])) {
|
||||
include_once($legacyMappging[$legacyClass]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::externalLibs($className)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
foreach (self::getNamespacesMapping() as $namespace => $mappedPath) {
|
||||
if (strpos($className, $namespace) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filepath = $mappedPath . str_replace('\\', '/', substr($className, strlen($namespace))) . '.php';
|
||||
if (file_exists($filepath)) {
|
||||
include_once($filepath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load external libs
|
||||
*
|
||||
* @param string $className class name
|
||||
*
|
||||
* @return bool return true if class is loaded
|
||||
*/
|
||||
protected static function externalLibs($className)
|
||||
{
|
||||
switch (strtolower(ltrim($className, '\\'))) {
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mappgin of some legacy classes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function customLegacyMapping()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return namespace mapping
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected static function getNamespacesMapping()
|
||||
{
|
||||
// the order is important, it is necessary to insert the longest namespaces first
|
||||
return array(
|
||||
self::ROOT_INSTALLER_NAMESPACE => DUPLICATOR_LITE_PATH . '/installer/dup-installer/src/',
|
||||
self::ROOT_NAMESPACE => DUPLICATOR_LITE_PATH . '/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, $needle)
|
||||
{
|
||||
$length = strlen($needle);
|
||||
if ($length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (substr($haystack, -$length) === $needle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\CachesPurge;
|
||||
|
||||
use DUP_Log;
|
||||
use Error;
|
||||
use Exception;
|
||||
|
||||
class CacheItem
|
||||
{
|
||||
/**
|
||||
* name of purge element (usualli plugin name)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* check function, returns true if the element is to be purged
|
||||
*
|
||||
* @var callable|bool
|
||||
*/
|
||||
protected $checkCallback = null;
|
||||
|
||||
/**
|
||||
* Purge cache callback
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected $purgeCallback = null;
|
||||
|
||||
/**
|
||||
* Message when cache is purged
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $purgedMessage = '';
|
||||
|
||||
/**
|
||||
* Construnctor
|
||||
*
|
||||
* @param string $name item name
|
||||
* @param bool|callable $checkCallback check callback, return true if cache of current item have to removed
|
||||
* @param callable $purgeCallback purge cache callback
|
||||
*/
|
||||
public function __construct($name, $checkCallback, $purgeCallback)
|
||||
{
|
||||
if (strlen($name) == 0) {
|
||||
throw new Exception('name can\'t be empty');
|
||||
}
|
||||
$this->name = $name;
|
||||
if (!is_bool($checkCallback) && !is_callable($checkCallback)) {
|
||||
throw new Exception('checkCallback must be boolean or callable');
|
||||
}
|
||||
$this->checkCallback = $checkCallback;
|
||||
|
||||
/* purge callback may not exist if the referenced plugin is not initialized.
|
||||
* That's why the check is performed only if you actually purge the plugin
|
||||
*/
|
||||
$this->purgeCallback = $purgeCallback;
|
||||
$this->purgedMessage = sprintf(__('All caches on <b>%s</b> have been purged.', 'duplicator'), $this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* overwrite default purged message
|
||||
*
|
||||
* @param string $message message if item have benn purged
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPurgedMessage($message)
|
||||
{
|
||||
$this->purgedMessage = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* purge caches item
|
||||
*
|
||||
* @param string $message message if item have benn purged
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function purge(&$message)
|
||||
{
|
||||
try {
|
||||
if (
|
||||
(is_bool($this->checkCallback) && $this->checkCallback) ||
|
||||
call_user_func($this->checkCallback) == true
|
||||
) {
|
||||
DUP_Log::trace('Purge ' . $this->name);
|
||||
if (!is_callable($this->purgeCallback)) {
|
||||
throw new Exception('purgeCallback must be callable');
|
||||
}
|
||||
call_user_func($this->purgeCallback);
|
||||
$message = $this->purgedMessage;
|
||||
}
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
DUP_Log::trace('Error purge ' . $this->name . ' message:' . $e->getMessage());
|
||||
$message = sprintf(__('Error on caches purge of <b>%s</b>.', 'duplicator'), $this->name);
|
||||
return false;
|
||||
} catch (Error $e) {
|
||||
DUP_Log::trace('Error purge ' . $this->name . ' message:' . $e->getMessage());
|
||||
$message = sprintf(__('Error on caches purge of <b>%s</b>.', 'duplicator'), $this->name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\CachesPurge;
|
||||
|
||||
class CachesPurge
|
||||
{
|
||||
/**
|
||||
* purge all and return purge messages
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function purgeAll()
|
||||
{
|
||||
$globalMessages = array();
|
||||
$items = array_merge(
|
||||
self::getPurgePlugins(),
|
||||
self::getPurgeHosts()
|
||||
);
|
||||
|
||||
|
||||
foreach ($items as $item) {
|
||||
$message = '';
|
||||
$result = $item->purge($message);
|
||||
if (strlen($message) > 0 && $result) {
|
||||
$globalMessages[] = $message;
|
||||
}
|
||||
}
|
||||
|
||||
return $globalMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* get list to cache items to purge
|
||||
*
|
||||
* @return CacheItem[]
|
||||
*/
|
||||
protected static function getPurgePlugins()
|
||||
{
|
||||
$items = array();
|
||||
$items[] = new CacheItem(
|
||||
'Elementor',
|
||||
function () {
|
||||
return class_exists("\\Elementor\\Plugin");
|
||||
},
|
||||
function () {
|
||||
\Elementor\Plugin::$instance->files_manager->clear_cache();
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'W3 Total Cache',
|
||||
function () {
|
||||
return function_exists('w3tc_pgcache_flush');
|
||||
},
|
||||
'w3tc_pgcache_flush'
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'WP Super Cache',
|
||||
function () {
|
||||
return function_exists('wp_cache_clear_cache');
|
||||
},
|
||||
'wp_cache_clear_cache'
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'WP Rocket',
|
||||
function () {
|
||||
return function_exists('rocket_clean_domain');
|
||||
},
|
||||
'rocket_clean_domain'
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Fast velocity minify',
|
||||
function () {
|
||||
return function_exists('fvm_purge_static_files');
|
||||
},
|
||||
'fvm_purge_static_files'
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Cachify',
|
||||
function () {
|
||||
return function_exists('cachify_flush_cache');
|
||||
},
|
||||
'cachify_flush_cache'
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Comet Cache',
|
||||
function () {
|
||||
return class_exists('\\comet_cache');
|
||||
},
|
||||
array('\\comet_cache', 'clear')
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Zen Cache',
|
||||
function () {
|
||||
return class_exists('\\zencache');
|
||||
},
|
||||
array('\\zencache', 'clear')
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'LiteSpeed Cache',
|
||||
function () {
|
||||
return has_action('litespeed_purge_all');
|
||||
},
|
||||
function () {
|
||||
return do_action('litespeed_purge_all');
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'WP Cloudflare Super Page Cache',
|
||||
function () {
|
||||
return class_exists('\\SW_CLOUDFLARE_PAGECACHE');
|
||||
},
|
||||
function () {
|
||||
return do_action("swcfpc_purge_everything");
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Hyper Cache',
|
||||
function () {
|
||||
return class_exists('\\HyperCache');
|
||||
},
|
||||
function () {
|
||||
return do_action('autoptimize_action_cachepurged');
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Cache Enabler',
|
||||
function () {
|
||||
return has_action('ce_clear_cache');
|
||||
},
|
||||
function () {
|
||||
return do_action('ce_clear_cache');
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'WP Fastest Cache',
|
||||
function () {
|
||||
return function_exists('wpfc_clear_all_cache');
|
||||
},
|
||||
function () {
|
||||
wpfc_clear_all_cache(true);
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Breeze',
|
||||
function () {
|
||||
return class_exists("\\Breeze_PurgeCache");
|
||||
},
|
||||
array('\\Breeze_PurgeCache', 'breeze_cache_flush')
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Swift Performance',
|
||||
function () {
|
||||
return class_exists("\\Swift_Performance_Cache");
|
||||
},
|
||||
array('\\Swift_Performance_Cache', 'clear_all_cache')
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Hummingbird',
|
||||
function () {
|
||||
return has_action('wphb_clear_page_cache');
|
||||
},
|
||||
function () {
|
||||
return do_action('wphb_clear_page_cache');
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'WP-Optimize',
|
||||
function () {
|
||||
return has_action('wpo_cache_flush');
|
||||
},
|
||||
function () {
|
||||
return do_action('wpo_cache_flush');
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Wordpress default',
|
||||
function () {
|
||||
return function_exists('wp_cache_flush');
|
||||
},
|
||||
'wp_cache_flush'
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Wordpress permalinks',
|
||||
function () {
|
||||
return function_exists('flush_rewrite_rules');
|
||||
},
|
||||
'flush_rewrite_rules'
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* get list to cache items to purge
|
||||
*
|
||||
* @return CacheItem[]
|
||||
*/
|
||||
protected static function getPurgeHosts()
|
||||
{
|
||||
$items = array();
|
||||
$items[] = new CacheItem(
|
||||
'Godaddy Managed WordPress Hosting',
|
||||
function () {
|
||||
return class_exists('\\WPaaS\\Plugin') && method_exists('\\WPass\\Plugin', 'vip');
|
||||
},
|
||||
function () {
|
||||
$method = 'BAN';
|
||||
$url = home_url();
|
||||
$host = wpraiser_get_domain();
|
||||
$url = set_url_scheme(str_replace($host, \WPaas\Plugin::vip(), $url), 'http');
|
||||
update_option('gd_system_last_cache_flush', time(), 'no'); # purge apc
|
||||
wp_remote_request(
|
||||
esc_url_raw($url),
|
||||
array(
|
||||
'method' => $method,
|
||||
'blocking' => false,
|
||||
'headers' =>
|
||||
array(
|
||||
'Host' => $host
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'SG Optimizer (Siteground)',
|
||||
function () {
|
||||
return function_exists('sg_cachepress_purge_everything');
|
||||
},
|
||||
'sg_cachepress_purge_everything'
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'WP Engine',
|
||||
function () {
|
||||
return (class_exists("\\WpeCommon") &&
|
||||
(method_exists('\\WpeCommon', 'purge_memcached') ||
|
||||
method_exists('\\WpeCommon', 'purge_varnish_cache')));
|
||||
},
|
||||
function () {
|
||||
if (method_exists('\\WpeCommon', 'purge_memcached')) {
|
||||
\WpeCommon::purge_memcached();
|
||||
}
|
||||
if (method_exists('\\WpeCommon', 'purge_varnish_cache')) {
|
||||
\WpeCommon::purge_varnish_cache();
|
||||
}
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Kinsta',
|
||||
function () {
|
||||
global $kinsta_cache;
|
||||
return (
|
||||
(isset($kinsta_cache) &&
|
||||
class_exists('\\Kinsta\\CDN_Enabler')) &&
|
||||
!empty($kinsta_cache->kinsta_cache_purge));
|
||||
},
|
||||
function () {
|
||||
global $kinsta_cache;
|
||||
$kinsta_cache->kinsta_cache_purge->purge_complete_caches();
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Pagely',
|
||||
function () {
|
||||
return class_exists('\\PagelyCachePurge');
|
||||
},
|
||||
function () {
|
||||
$purge_pagely = new \PagelyCachePurge();
|
||||
$purge_pagely->purgeAll();
|
||||
}
|
||||
);
|
||||
$items[] = new CacheItem(
|
||||
'Pressidum',
|
||||
function () {
|
||||
return defined('WP_NINUKIS_WP_NAME') && class_exists('\\Ninukis_Plugin');
|
||||
},
|
||||
function () {
|
||||
$purge_pressidum = \Ninukis_Plugin::get_instance();
|
||||
$purge_pressidum->purgeAllCaches();
|
||||
}
|
||||
);
|
||||
|
||||
$items[] = new CacheItem(
|
||||
'Pantheon Advanced Page Cache plugin',
|
||||
function () {
|
||||
return function_exists('pantheon_wp_clear_edge_all');
|
||||
},
|
||||
'pantheon_wp_clear_edge_all'
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
47
html/wp-content/plugins/duplicator/src/Utils/CronUtils.php
Normal file
47
html/wp-content/plugins/duplicator/src/Utils/CronUtils.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils;
|
||||
|
||||
class CronUtils
|
||||
{
|
||||
const INTERVAL_DAILTY = 'duplicator_daily_cron';
|
||||
const INTERVAL_WEEKLY = 'duplicator_weekly_cron';
|
||||
const INTERVAL_MONTHLY = 'duplicator_monthly_cron';
|
||||
|
||||
/**
|
||||
* Init WordPress hooks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
add_filter('cron_schedules', array(__CLASS__, 'defaultCronIntervals'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add duplicator pro cron schedules
|
||||
*
|
||||
* @param array<string, array<string,int|string>> $schedules schedules
|
||||
*
|
||||
* @return array<string, array<string,int|string>>
|
||||
*/
|
||||
public static function defaultCronIntervals($schedules)
|
||||
{
|
||||
$schedules[self::INTERVAL_DAILTY] = array(
|
||||
'interval' => DAY_IN_SECONDS,
|
||||
'display' => __('Once a Day', 'duplicator'),
|
||||
);
|
||||
|
||||
$schedules[self::INTERVAL_WEEKLY] = array(
|
||||
'interval' => WEEK_IN_SECONDS,
|
||||
'display' => __('Once a Week', 'duplicator'),
|
||||
);
|
||||
|
||||
$schedules[self::INTERVAL_MONTHLY] = array(
|
||||
'interval' => MONTH_IN_SECONDS,
|
||||
'display' => __('Once a Month', 'duplicator'),
|
||||
);
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* These functions are performed before including any other Duplicator file so
|
||||
* do not use any Duplicator library or feature and use code compatible with PHP 5.2
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// In the future it will be included on both PRO and LITE so you need to check if the define exists.
|
||||
if (!class_exists('DuplicatorPhpVersionCheck')) {
|
||||
|
||||
class DuplicatorPhpVersionCheck // phpcs:ignore
|
||||
{
|
||||
/** @var string */
|
||||
protected static $minVer = '';
|
||||
/** @var string */
|
||||
protected static $suggestedVer = '';
|
||||
|
||||
/**
|
||||
* Check PhpVersin
|
||||
*
|
||||
* @param string $minVer min version of PHP
|
||||
* @param string $suggestedVer suggested version of PHP
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function check($minVer, $suggestedVer)
|
||||
{
|
||||
self::$minVer = $minVer;
|
||||
self::$suggestedVer = $suggestedVer;
|
||||
|
||||
if (version_compare(PHP_VERSION, self::$minVer, '<')) {
|
||||
if (is_multisite()) {
|
||||
add_action('network_admin_notices', array(__CLASS__, 'notice'));
|
||||
} else {
|
||||
add_action('admin_notices', array(__CLASS__, 'notice'));
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display notice
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function notice()
|
||||
{
|
||||
if (preg_match('/^(\d+\.\d+(?:\.\d+)?)/', PHP_VERSION, $matches) === 1) {
|
||||
$phpVersion = $matches[1];
|
||||
} else {
|
||||
$phpVersion = PHP_VERSION;
|
||||
}
|
||||
?>
|
||||
<div class="error notice">
|
||||
<p>
|
||||
<?php
|
||||
echo wp_kses(
|
||||
sprintf(
|
||||
__(
|
||||
'DUPLICATOR: Action Required - <b>PHP Version Update Needed</b>, Your site is running PHP version %s.',
|
||||
'duplicator'
|
||||
),
|
||||
esc_html($phpVersion)
|
||||
),
|
||||
[
|
||||
'b' => [],
|
||||
]
|
||||
);
|
||||
?><br><br>
|
||||
<?php
|
||||
echo wp_kses(
|
||||
sprintf(
|
||||
__(
|
||||
'Starting from <b>Duplicator %1$s</b>, Duplicator will require <b>PHP %2$s or higher</b> to receive new updates.',
|
||||
'duplicator'
|
||||
),
|
||||
'1.5.12',
|
||||
esc_html(self::$minVer)
|
||||
),
|
||||
[
|
||||
'b' => [],
|
||||
]
|
||||
);
|
||||
?><br>
|
||||
<?php
|
||||
esc_html_e(
|
||||
'While your current version of Duplicator will continue to work,
|
||||
you\'ll need to upgrade your PHP version to receive future features, improvements, and security updates.',
|
||||
'duplicator'
|
||||
);
|
||||
?><br>
|
||||
<?php
|
||||
esc_html_e(
|
||||
'Please contact your hosting provider to upgrade your PHP version.',
|
||||
'duplicator'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://duplicator.com/knowledge-base/updating-your-php-version-in-wordpress/" target="_blank">
|
||||
<?php esc_html_e('Learn more about this change and how to upgrade', 'duplicator'); ?>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\Email;
|
||||
|
||||
class EmailHelper
|
||||
{
|
||||
/** @var array<string, array<string, string>> List of styles in class => styles format*/
|
||||
public static $styles = array(
|
||||
'body' => array(
|
||||
"border-collapse" => "collapse",
|
||||
"border-spacing" => "0",
|
||||
"vertical-align" => "top",
|
||||
"mso-table-lspace" => "0pt",
|
||||
"mso-table-rspace" => "0pt",
|
||||
"-ms-text-size-adjust" => "100%",
|
||||
"-webkit-text-size-adjust" => "100%",
|
||||
"height" => "100% !important",
|
||||
"width" => "100% !important",
|
||||
"min-width" => "100%",
|
||||
"-moz-box-sizing" => "border-box",
|
||||
"-webkit-box-sizing" => "border-box",
|
||||
"box-sizing" => "border-box",
|
||||
"-webkit-font-smoothing" => "antialiased !important",
|
||||
"-moz-osx-font-smoothing" => "grayscale !important",
|
||||
"background-color" => "#e9eaec",
|
||||
"color" => "#444444",
|
||||
"font-family" => "'Helvetica Neue', Helvetica, Arial, sans-serif",
|
||||
"font-weight" => "normal",
|
||||
"padding" => "0",
|
||||
"margin" => "0",
|
||||
"text-align" => "left",
|
||||
"font-size" => "14px",
|
||||
"mso-line-height-rule" => "exactly",
|
||||
"line-height" => "140%",
|
||||
),
|
||||
'table' => array(
|
||||
"border-collapse" => "collapse",
|
||||
"border-spacing" => "0",
|
||||
"vertical-align" => "top",
|
||||
"mso-table-lspace" => "0pt",
|
||||
"mso-table-rspace" => "0pt",
|
||||
"-ms-text-size-adjust" => "100%",
|
||||
"-webkit-text-size-adjust" => "100%",
|
||||
"margin" => "0 auto 0 auto",
|
||||
"padding" => "0",
|
||||
"text-align" => "inherit",
|
||||
),
|
||||
'main-tbl' => array(
|
||||
"width" => "600px",
|
||||
),
|
||||
'stats-tbl' => array(
|
||||
"-ms-text-size-adjust" => "100%",
|
||||
"-webkit-text-size-adjust" => "100%",
|
||||
"width" => "100%",
|
||||
"margin" => "15px 0 38px 0",
|
||||
),
|
||||
'tr' => array(
|
||||
"padding" => "0",
|
||||
"vertical-align" => "top",
|
||||
"text-align" => "left",
|
||||
),
|
||||
'td' => array(
|
||||
"word-wrap" => "break-word",
|
||||
"-webkit-hyphens" => "auto",
|
||||
"-moz-hyphens" => "auto",
|
||||
"hyphens" => "auto",
|
||||
"border-collapse" => "collapse !important",
|
||||
"vertical-align" => "top",
|
||||
"mso-table-lspace" => "0pt",
|
||||
"mso-table-rspace" => "0pt",
|
||||
"-ms-text-size-adjust" => "100%",
|
||||
"-webkit-text-size-adjust" => "100%",
|
||||
"color" => "#444444",
|
||||
"font-family" => "'Helvetica Neue', Helvetica, Arial, sans-serif",
|
||||
"font-weight" => "normal",
|
||||
"padding" => "0",
|
||||
"margin" => "0",
|
||||
"font-size" => "14px",
|
||||
"mso-line-height-rule" => "exactly",
|
||||
"line-height" => "140%",
|
||||
),
|
||||
'stats-count-cell' => array(
|
||||
'width' => '1px',
|
||||
'text-align' => 'center',
|
||||
),
|
||||
'unsubscribe' => array(
|
||||
"padding" => "30px",
|
||||
"color" => "#72777c",
|
||||
"font-size" => "12px",
|
||||
"text-align" => "center",
|
||||
),
|
||||
'th' => array(
|
||||
"font-family" => "'Helvetica Neue', Helvetica, Arial, sans-serif",
|
||||
"margin" => "0",
|
||||
"text-align" => "left",
|
||||
"font-size" => "14px",
|
||||
"mso-line-height-rule" => "exactly",
|
||||
"line-height" => "140%",
|
||||
"font-weight" => "700",
|
||||
"color" => "#777777",
|
||||
"background" => "#f1f1f1",
|
||||
"border" => "1px solid #f1f1f1",
|
||||
"padding" => "17px 20px 17px 20px",
|
||||
),
|
||||
'stats-cell' => array(
|
||||
"font-size" => "16px",
|
||||
"border-top" => "none",
|
||||
"border-right" => "none",
|
||||
"border-bottom" => "1px solid #f1f1f1",
|
||||
"border-left" => "none",
|
||||
"color" => "#444444",
|
||||
"padding" => "17px 20px 17px 20px",
|
||||
),
|
||||
'img' => array(
|
||||
"outline" => "none",
|
||||
"text-decoration" => "none",
|
||||
"width" => "auto",
|
||||
"clear" => "both",
|
||||
"-ms-interpolation-mode" => "bicubic",
|
||||
"display" => "inline-block !important",
|
||||
"max-width" => "45%",
|
||||
),
|
||||
'h6' => array(
|
||||
"padding" => "0",
|
||||
"text-align" => "left",
|
||||
"word-wrap" => "normal",
|
||||
"font-family" => "'Helvetica Neue', Helvetica, Arial, sans-serif",
|
||||
"font-weight" => "bold",
|
||||
"mso-line-height-rule" => "exactly",
|
||||
"line-height" => "130%",
|
||||
"font-size" => "18px",
|
||||
"color" => "#444444",
|
||||
"margin" => "0 0 3px 0",
|
||||
),
|
||||
'p' => array(
|
||||
"-ms-text-size-adjust" => "100%",
|
||||
"-webkit-text-size-adjust" => "100%",
|
||||
"font-family" => "'Helvetica Neue', Helvetica, Arial, sans-serif",
|
||||
"font-weight" => "normal",
|
||||
"padding" => "0",
|
||||
"text-align" => "left",
|
||||
"mso-line-height-rule" => "exactly",
|
||||
"line-height" => "140%",
|
||||
"overflow-wrap" => "break-word",
|
||||
"word-wrap" => "break-word",
|
||||
"-ms-word-break" => "break-all",
|
||||
"word-break" => "break-word",
|
||||
"-ms-hyphens" => "auto",
|
||||
"-moz-hyphens" => "auto",
|
||||
"-webkit-hyphens" => "auto",
|
||||
"hyphens" => "auto",
|
||||
"color" => "#777777",
|
||||
"font-size" => "14px",
|
||||
"margin" => "25px 0 25px 0",
|
||||
),
|
||||
'a' => array(
|
||||
"-ms-text-size-adjust" => "100%",
|
||||
"-webkit-text-size-adjust" => "100%",
|
||||
"font-family" => "'Helvetica Neue', Helvetica, Arial, sans-serif",
|
||||
"font-weight" => "normal",
|
||||
"padding" => "0",
|
||||
"margin" => "0",
|
||||
"Margin" => "0",
|
||||
"text-align" => "left",
|
||||
"mso-line-height-rule" => "exactly",
|
||||
"line-height" => "140%",
|
||||
),
|
||||
'footer-link' => array(
|
||||
"color" => "#72777c",
|
||||
"text-decoration" => "underline",
|
||||
),
|
||||
'inline-link' => array(
|
||||
"color" => "inherit",
|
||||
"text-decoration" => "underline",
|
||||
),
|
||||
'stats-title' => array(
|
||||
"margin" => "0 0 15px 0",
|
||||
),
|
||||
'subtitle' => array(
|
||||
"font-size" => "16px",
|
||||
"margin" => "0 0 15px 0",
|
||||
),
|
||||
'txt-orange' => array(
|
||||
"color" => "#e27730",
|
||||
),
|
||||
'txt-center' => array(
|
||||
'text-align' => 'center',
|
||||
),
|
||||
'logo' => array(
|
||||
"padding" => "30px 0px",
|
||||
),
|
||||
'content' => array(
|
||||
"background-color" => "#ffffff",
|
||||
"padding" => "60px 75px 45px 75px",
|
||||
"border-top" => "3px solid #e27730",
|
||||
"border-right" => "1px solid #dddddd",
|
||||
"border-bottom" => "1px solid #dddddd",
|
||||
"border-left" => "1px solid #dddddd",
|
||||
),
|
||||
'strong' => array(
|
||||
"font-weight" => "bold",
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Get Inline CSS of selector or empty if selector not found
|
||||
*
|
||||
* @param string $selectors Space separated selectors
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getStyle($selectors)
|
||||
{
|
||||
if ($selectors === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$selArr = explode(' ', $selectors);
|
||||
$uniqueStyles = array();
|
||||
foreach ($selArr as $i => $selector) {
|
||||
if (!isset(self::$styles[$selector])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//overwrite repeating styles
|
||||
foreach (self::$styles[$selector] as $key => $value) {
|
||||
$uniqueStyles[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$style = '';
|
||||
foreach ($uniqueStyles as $key => $value) {
|
||||
$style .= $key . ': ' . $value . ';';
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print Inline CSS of selector or empty if selector not found
|
||||
*
|
||||
* @param string $selectors Space separated selectors
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function printStyle($selectors)
|
||||
{
|
||||
echo 'class="' . esc_attr($selectors) . '" style="' . self::getStyle($selectors) . '"';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\Email;
|
||||
|
||||
use DUP_Package;
|
||||
use DUP_Settings;
|
||||
use DUP_PackageStatus;
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Libs\Snap\JsonSerialize\JsonSerialize;
|
||||
|
||||
/**
|
||||
* Email Summary
|
||||
*/
|
||||
class EmailSummary
|
||||
{
|
||||
const SEND_FREQ_NEVER = 'never';
|
||||
const SEND_FREQ_DAILY = 'daily';
|
||||
const SEND_FREQ_WEEKLY = 'weekly';
|
||||
const SEND_FREQ_MONTHLY = 'monthly';
|
||||
|
||||
/**
|
||||
* Old option key for storing email summary info, used for migrating data to the correct option key
|
||||
*/
|
||||
const INFO_OPT_OLD_KEY = 'duplicator-email-summary-info';
|
||||
const PEVIEW_SLUG = 'duplicator-email-summary-preview';
|
||||
const INFO_OPT_KEY = 'duplicator_email_summary_info';
|
||||
|
||||
/** @var self The singleton instance */
|
||||
private static $self = null;
|
||||
|
||||
/** @var int[] Manual package ids */
|
||||
private $manualPackageIds = array();
|
||||
|
||||
/** @var int[] info about created storages*/
|
||||
private $failedPackageIds = array();
|
||||
|
||||
/**
|
||||
* Get the singleton instance
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$self == null) {
|
||||
self::$self = new self();
|
||||
}
|
||||
|
||||
return self::$self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Email Summary object
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
if (($data = get_option(self::INFO_OPT_KEY)) !== false) {
|
||||
JsonSerialize::unserializeToObj($data, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preview link
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getPreviewLink()
|
||||
{
|
||||
return ControllersManager::getMenuLink(self::PEVIEW_SLUG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add package to summary
|
||||
*
|
||||
* @param DUP_Package $package The package
|
||||
* @param int $status The status
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPackage(DUP_Package $package, $status)
|
||||
{
|
||||
if ($status !== DUP_PackageStatus::COMPLETE && $status !== DUP_PackageStatus::ERROR) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($status === DUP_PackageStatus::COMPLETE) {
|
||||
$this->manualPackageIds[] = $package->ID;
|
||||
} elseif ($status === DUP_PackageStatus::ERROR) {
|
||||
$this->failedPackageIds[] = $package->ID;
|
||||
}
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns info about created packages
|
||||
*
|
||||
* @return array<int|string, array<string, string|int>>
|
||||
*/
|
||||
public function getPackagesInfo()
|
||||
{
|
||||
$packagesInfo = array();
|
||||
$packagesInfo['manual'] = array(
|
||||
'name' => __('Successful', 'duplicator'),
|
||||
'count' => count($this->manualPackageIds),
|
||||
);
|
||||
|
||||
$packagesInfo['failed'] = array(
|
||||
'name' => __('Failed', 'duplicator'),
|
||||
'count' => count($this->failedPackageIds),
|
||||
);
|
||||
|
||||
return $packagesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all frequency options
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function getAllFrequencyOptions()
|
||||
{
|
||||
return array(
|
||||
self::SEND_FREQ_NEVER => esc_html__('Never', 'duplicator'),
|
||||
self::SEND_FREQ_DAILY => esc_html__('Daily', 'duplicator'),
|
||||
self::SEND_FREQ_WEEKLY => esc_html__('Weekly', 'duplicator'),
|
||||
self::SEND_FREQ_MONTHLY => esc_html__('Monthly', 'duplicator'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frequency text displayed in the email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getFrequencyText()
|
||||
{
|
||||
$frequency = DUP_Settings::Get('email_summary_frequency');
|
||||
switch ($frequency) {
|
||||
case self::SEND_FREQ_DAILY:
|
||||
return esc_html__('day', 'duplicator');
|
||||
case self::SEND_FREQ_MONTHLY:
|
||||
return esc_html__('month', 'duplicator');
|
||||
case self::SEND_FREQ_WEEKLY:
|
||||
default:
|
||||
return esc_html__('week', 'duplicator');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset plugin data
|
||||
*
|
||||
* @return bool True if data has been reset, false otherwise
|
||||
*/
|
||||
public function resetData()
|
||||
{
|
||||
$this->manualPackageIds = array();
|
||||
$this->failedPackageIds = array();
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save plugin data
|
||||
*
|
||||
* @return bool True if data has been saved, false otherwise
|
||||
*/
|
||||
private function save()
|
||||
{
|
||||
return update_option(self::INFO_OPT_KEY, JsonSerialize::serialize($this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\Email;
|
||||
|
||||
use DUP_Log;
|
||||
use DUP_Package;
|
||||
use DUP_Settings;
|
||||
use DUP_PackageStatus;
|
||||
use Duplicator\Utils\CronUtils;
|
||||
use Duplicator\Libs\Snap\SnapWP;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
|
||||
/**
|
||||
* Email summary bootstrap
|
||||
*/
|
||||
class EmailSummaryBootstrap
|
||||
{
|
||||
const CRON_HOOK = 'duplicator_email_summary_cron';
|
||||
|
||||
/**
|
||||
* Init Email Summaries
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
//Package hooks
|
||||
add_action('duplicator_package_after_set_status', array(__CLASS__, 'addPackage'), 10, 2);
|
||||
|
||||
//Set cron action
|
||||
add_action(self::CRON_HOOK, array(__CLASS__, 'send'));
|
||||
|
||||
//Activation/deactivation hooks
|
||||
add_action('duplicator_after_activation', array(__CLASS__, 'activationAction'));
|
||||
add_action('duplicator_after_deactivation', array(__CLASS__, 'deactivationAction'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add package to summary
|
||||
*
|
||||
* @param DUP_Package $package The package
|
||||
* @param int $status The status
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addPackage(DUP_Package $package, $status)
|
||||
{
|
||||
EmailSummary::getInstance()->addPackage($package, $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email summary
|
||||
*
|
||||
* @return bool True if email was sent
|
||||
*/
|
||||
public static function send()
|
||||
{
|
||||
$frequency = DUP_Settings::Get('email_summary_frequency');
|
||||
if (($recipient = get_option('admin_email')) === false || $frequency === EmailSummary::SEND_FREQ_NEVER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parsedHomeUrl = wp_parse_url(home_url());
|
||||
$siteDomain = $parsedHomeUrl['host'];
|
||||
|
||||
if (is_multisite() && isset($parsedHomeUrl['path'])) {
|
||||
$siteDomain .= $parsedHomeUrl['path'];
|
||||
}
|
||||
|
||||
$subject = sprintf(
|
||||
esc_html_x(
|
||||
'Your Weekly Duplicator Summary for %s',
|
||||
'%s is the site domain',
|
||||
'duplicator'
|
||||
),
|
||||
$siteDomain
|
||||
);
|
||||
|
||||
$content = TplMng::getInstance()->render('mail/email_summary', array(
|
||||
'packages' => EmailSummary::getInstance()->getPackagesInfo(),
|
||||
), false);
|
||||
|
||||
add_filter('wp_mail_content_type', array(__CLASS__, 'getMailContentType'));
|
||||
if (!wp_mail($recipient, $subject, $content)) {
|
||||
DUP_Log::Trace("FAILED TO SEND EMAIL SUMMARY.");
|
||||
DUP_Log::Trace("Recipients: " . $recipient);
|
||||
return false;
|
||||
} elseif (!EmailSummary::getInstance()->resetData()) {
|
||||
DUP_Log::Trace("FAILED TO RESET EMAIL SUMMARY DATA.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mail content type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMailContentType()
|
||||
{
|
||||
return 'text/html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Activation action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function activationAction()
|
||||
{
|
||||
$frequency = DUP_Settings::Get('email_summary_frequency');
|
||||
if ($frequency === EmailSummary::SEND_FREQ_NEVER) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self::updateCron($frequency) == false) {
|
||||
DUP_Log::Trace("FAILED TO INIT EMAIL SUMMARY CRON. Frequency: {$frequency}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivation action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function deactivationAction()
|
||||
{
|
||||
if (self::updateCron(EmailSummary::SEND_FREQ_NEVER) == false) {
|
||||
DUP_Log::Trace("FAILED TO REMOVE EMAIL SUMMARY CRON.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update next send time on frequency setting change
|
||||
*
|
||||
* @param string $oldFrequency The old frequency
|
||||
* @param string $newFrequency The new frequency
|
||||
*
|
||||
* @return bool True if the cron was updated or false on error
|
||||
*/
|
||||
public static function updateFrequency($oldFrequency, $newFrequency)
|
||||
{
|
||||
if ($oldFrequency === $newFrequency) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return self::updateCron($newFrequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the WP Cron job base on frequency or settings
|
||||
*
|
||||
* @param string $frequency The frequency
|
||||
*
|
||||
* @return bool True if the cron was updated or false on error
|
||||
*/
|
||||
private static function updateCron($frequency = '')
|
||||
{
|
||||
if (strlen($frequency) === 0) {
|
||||
$frequency = DUP_Settings::Get('email_summary_frequency');
|
||||
}
|
||||
|
||||
if ($frequency === EmailSummary::SEND_FREQ_NEVER) {
|
||||
if (wp_next_scheduled(self::CRON_HOOK)) {
|
||||
//have to check return like this because
|
||||
//wp_clear_scheduled_hook returns void in WP < 5.1
|
||||
return !self::isFalseOrWpError(wp_clear_scheduled_hook(self::CRON_HOOK));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
wp_next_scheduled(self::CRON_HOOK)
|
||||
&& self::isFalseOrWpError(wp_clear_scheduled_hook(self::CRON_HOOK))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !self::isFalseOrWpError(wp_schedule_event(
|
||||
self::getFirstRunTime($frequency),
|
||||
self::getCronSchedule($frequency),
|
||||
self::CRON_HOOK
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set next send time based on frequency
|
||||
*
|
||||
* @param string $frequency Frequency
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function getFirstRunTime($frequency)
|
||||
{
|
||||
switch ($frequency) {
|
||||
case EmailSummary::SEND_FREQ_DAILY:
|
||||
$firstRunTime = strtotime('tomorrow 14:00');
|
||||
break;
|
||||
case EmailSummary::SEND_FREQ_WEEKLY:
|
||||
$firstRunTime = strtotime('next monday 14:00');
|
||||
break;
|
||||
case EmailSummary::SEND_FREQ_MONTHLY:
|
||||
$firstRunTime = strtotime('first day of next month 14:00');
|
||||
break;
|
||||
case EmailSummary::SEND_FREQ_NEVER:
|
||||
return 0;
|
||||
default:
|
||||
throw new \Exception("Unknown frequency: " . $frequency);
|
||||
}
|
||||
|
||||
return $firstRunTime - SnapWP::getGMTOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cron schedule
|
||||
*
|
||||
* @param string $frequency The frequency
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getCronSchedule($frequency)
|
||||
{
|
||||
switch ($frequency) {
|
||||
case EmailSummary::SEND_FREQ_DAILY:
|
||||
return CronUtils::INTERVAL_DAILTY;
|
||||
case EmailSummary::SEND_FREQ_WEEKLY:
|
||||
return CronUtils::INTERVAL_WEEKLY;
|
||||
case EmailSummary::SEND_FREQ_MONTHLY:
|
||||
return CronUtils::INTERVAL_MONTHLY;
|
||||
default:
|
||||
throw new Exception("Unknown frequency: " . $frequency);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if is false or wp_error
|
||||
*
|
||||
* @param mixed $value The value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isFalseOrWpError($value)
|
||||
{
|
||||
return $value === false || is_wp_error($value);
|
||||
}
|
||||
}
|
||||
164
html/wp-content/plugins/duplicator/src/Utils/ExpireOptions.php
Normal file
164
html/wp-content/plugins/duplicator/src/Utils/ExpireOptions.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Expire options
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Utils;
|
||||
|
||||
use Duplicator\Libs\Snap\JsonSerialize\JsonSerialize;
|
||||
use Duplicator\Libs\Snap\SnapDB;
|
||||
|
||||
final class ExpireOptions
|
||||
{
|
||||
const OPTION_PREFIX = 'duplicator_expire_';
|
||||
|
||||
/** @var array<string, array{expire: int, value: mixed}> */
|
||||
private static $cacheOptions = array();
|
||||
|
||||
|
||||
/**
|
||||
* Sets/updates the value of a expire option.
|
||||
*
|
||||
* You do not need to serialize values. If the value needs to be serialized,
|
||||
* then it will be serialized before it is set.
|
||||
*
|
||||
* @param string $key Expire option key.
|
||||
* @param mixed $value Option value.
|
||||
* @param int $expiration Optional. Time until expiration in seconds. Default 0 (no expiration).
|
||||
*
|
||||
* @return bool True if the value was set, false otherwise.
|
||||
*/
|
||||
public static function set($key, $value, $expiration = 0)
|
||||
{
|
||||
$time = ($expiration > 0 ? time() + $expiration : 0);
|
||||
|
||||
self::$cacheOptions[$key] = array(
|
||||
'expire' => $time,
|
||||
'value' => $value,
|
||||
);
|
||||
|
||||
return update_option(self::OPTION_PREFIX . $key, JsonSerialize::serialize(self::$cacheOptions[$key]), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a expire option.
|
||||
*
|
||||
* If the option does not exist, does not have a value, or has expired,
|
||||
* then the return value will be false.
|
||||
*
|
||||
* @param string $key Expire option key.
|
||||
* @param mixed $default Return this value if option don\'t exists os is expired
|
||||
*
|
||||
* @return mixed Value of transient.
|
||||
*/
|
||||
public static function get($key, $default = false)
|
||||
{
|
||||
if (!isset(self::$cacheOptions[$key])) {
|
||||
if (($option = get_option(self::OPTION_PREFIX . $key)) == false) {
|
||||
self::$cacheOptions[$key] = self::unexistsKeyValue();
|
||||
} else {
|
||||
self::$cacheOptions[$key] = JsonSerialize::unserialize($option);
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$cacheOptions[$key]['expire'] < 0) {
|
||||
// don't exists the wp-option
|
||||
return $default;
|
||||
}
|
||||
|
||||
if (self::$cacheOptions[$key]['expire'] > 0 && self::$cacheOptions[$key]['expire'] < time()) {
|
||||
// if 0 don't expire so check only if time is > 0
|
||||
self::delete($key);
|
||||
return $default;
|
||||
}
|
||||
|
||||
return self::$cacheOptions[$key]['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns the value of the option or false if it has expired. In case the option has expired then it is updated.
|
||||
* It does the same thing as a get and a set but with one less query.
|
||||
*
|
||||
* @param string $key Expire option key.
|
||||
* @param mixed $value Option value.
|
||||
* @param int $expiration Optional. Time until expiration in seconds. Default 0 (no expiration).
|
||||
*
|
||||
* @return mixed Value of transient.
|
||||
*/
|
||||
public static function getUpdate($key, $value, $expiration = 0)
|
||||
{
|
||||
if (!isset(self::$cacheOptions[$key])) {
|
||||
if (($option = get_option(self::OPTION_PREFIX . $key)) == false) {
|
||||
self::$cacheOptions[$key] = self::unexistsKeyValue();
|
||||
} else {
|
||||
self::$cacheOptions[$key] = JsonSerialize::unserialize($option);
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$cacheOptions[$key]['expire'] < time()) {
|
||||
self::set($key, $value, $expiration);
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::$cacheOptions[$key]['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a option
|
||||
*
|
||||
* @param string $key Expire option key. Expected to not be SQL-escaped.
|
||||
*
|
||||
* @return bool True if the option was deleted, false otherwise.
|
||||
*/
|
||||
public static function delete($key)
|
||||
{
|
||||
if (delete_option(self::OPTION_PREFIX . $key)) {
|
||||
self::$cacheOptions[$key] = self::unexistsKeyValue();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function deleteAll()
|
||||
{
|
||||
/** @var \wpdb $wpdb */
|
||||
global $wpdb;
|
||||
|
||||
$optionsTableName = esc_sql($wpdb->base_prefix . "options");
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT `option_name` FROM `{$optionsTableName}` WHERE `option_name` REGEXP %s",
|
||||
SnapDB::quoteRegex(self::OPTION_PREFIX)
|
||||
);
|
||||
$dupOptionNames = $wpdb->get_col($query);
|
||||
|
||||
foreach ($dupOptionNames as $dupOptionName) {
|
||||
delete_option($dupOptionName);
|
||||
}
|
||||
self::$cacheOptions = array();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value for unexists key option
|
||||
*
|
||||
* @return array{expire: int, value: false}
|
||||
*/
|
||||
private static function unexistsKeyValue()
|
||||
{
|
||||
return array(
|
||||
'expire' => -1,
|
||||
'value' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\ExtraPlugins;
|
||||
|
||||
use DUP_LITE_Plugin_Upgrade;
|
||||
use Duplicator\Controllers\AboutUsController;
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Core\Notifications\Notice;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
|
||||
class CrossPromotion
|
||||
{
|
||||
const PLUGINS_LIMIT = 3;
|
||||
|
||||
/** @var string */
|
||||
const NOTICE_SLUG = 'duplicator_cross_promotion';
|
||||
|
||||
/**
|
||||
* Init notice
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
if (!current_user_can('install_plugins')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$installInfo = DUP_LITE_Plugin_Upgrade::getInstallInfo();
|
||||
if ($installInfo['updateTime'] + (2 * WEEK_IN_SECONDS) > time()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ControllersManager::isCurrentPage(ControllersManager::MAIN_MENU_SLUG)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugins = self::getExtraPlugins();
|
||||
if (count($plugins) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
AboutUsController::enqueueScripts();
|
||||
|
||||
Notice::add(
|
||||
TplMng::getInstance()->render(
|
||||
'parts/cross_promotion/list',
|
||||
[
|
||||
'plugins' => $plugins,
|
||||
'limit' => self::PLUGINS_LIMIT,
|
||||
],
|
||||
false
|
||||
),
|
||||
self::NOTICE_SLUG,
|
||||
'',
|
||||
[
|
||||
'autop' => false,
|
||||
'dismiss' => Notice::DISMISS_USER,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extra plugins to be promoted
|
||||
*
|
||||
* @return ExtraItem[]
|
||||
*/
|
||||
public static function getExtraPlugins()
|
||||
{
|
||||
$slugs = self::getSlugs();
|
||||
$plugins = [];
|
||||
$extraPluginsMng = ExtraPluginsMng::getInstance();
|
||||
|
||||
foreach ($slugs as $slug) {
|
||||
if (count($plugins) >= self::PLUGINS_LIMIT) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (($plugin = $extraPluginsMng->getBySlug($slug)) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($plugin->isInstalled() || !$plugin->checkRequirments()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugins[] = $plugin;
|
||||
}
|
||||
|
||||
foreach ($extraPluginsMng->getAll() as $plugin) {
|
||||
if (count($plugins) >= self::PLUGINS_LIMIT) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_array($plugin->getSlug(), $slugs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($plugin->isInstalled() || !$plugin->checkRequirments()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugins[] = $plugin;
|
||||
}
|
||||
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the slugs of the extra plugins to be promoted with priority
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected static function getSlugs()
|
||||
{
|
||||
return [
|
||||
'search-replace-wpcode/wsrw.php',
|
||||
'wp-mail-smtp/wp_mail_smtp.php',
|
||||
'insert-headers-and-footers/ihaf.php',
|
||||
'all-in-one-seo-pack/all_in_one_seo_pack.php',
|
||||
'wpforms-lite/wpforms.php',
|
||||
'uncanny-automator/uncanny-automator.php',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\ExtraPlugins;
|
||||
|
||||
use DUP_Log;
|
||||
use Duplicator\Libs\Snap\SnapString;
|
||||
use Duplicator\Utils\ExpireOptions;
|
||||
|
||||
class ExtraItem
|
||||
{
|
||||
const STATUS_NOT_INSTALLED = 0;
|
||||
const STATUS_INSTALLED = 1;
|
||||
const STATUS_ACTIVE = 2;
|
||||
const URL_TYPE_GENERIC = 0;
|
||||
const URL_TYPE_ZIP = 1;
|
||||
const PLUGIN_API_FIELDS = [
|
||||
'active_installs' => false,
|
||||
'added' => false,
|
||||
'author' => false,
|
||||
'author_block_count' => false,
|
||||
'author_block_rating' => false,
|
||||
'author_profile' => false,
|
||||
'banners' => false,
|
||||
'compatibility' => false,
|
||||
'contributors' => false,
|
||||
'description' => false,
|
||||
'donate_link' => false,
|
||||
'download_link' => false,
|
||||
'downloaded' => false,
|
||||
'group' => false,
|
||||
'homepage' => false,
|
||||
'icons' => false,
|
||||
'last_updated' => false,
|
||||
'name' => false,
|
||||
'num_ratings' => false,
|
||||
'rating' => false,
|
||||
'ratings' => false,
|
||||
'requires' => true,
|
||||
'requires_php' => true,
|
||||
'reviews' => false,
|
||||
'screenshots' => false,
|
||||
'sections' => false,
|
||||
'short_description' => false,
|
||||
'slug' => false,
|
||||
'support_threads' => false,
|
||||
'support_threads_resolved' => false,
|
||||
'tags' => false,
|
||||
'tested' => false,
|
||||
'version' => false,
|
||||
'versions' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* plugin name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name = '';
|
||||
|
||||
/**
|
||||
* plugin slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $slug = '';
|
||||
|
||||
/**
|
||||
* url to plugin icon
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $icon = '';
|
||||
|
||||
/**
|
||||
* plugin description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $desc = '';
|
||||
|
||||
/**
|
||||
* plugin url either to zip file or pro version
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $url = '';
|
||||
|
||||
/**
|
||||
* plugin url on wordpress.org if available
|
||||
*
|
||||
* @var bool|string
|
||||
*/
|
||||
public $wpOrgURL = '';
|
||||
|
||||
/**
|
||||
* PRO version of plugin if available
|
||||
*
|
||||
* @var ExtraItem|null
|
||||
*/
|
||||
protected $pro = null;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param string $name plugin name
|
||||
* @param string $slug plugin slug
|
||||
* @param string $icon url to plugin icon
|
||||
* @param string $desc plugin description
|
||||
* @param string $url plugin url
|
||||
* @param string|bool $wpOrgURL plugin url on wordpress.org
|
||||
*/
|
||||
public function __construct($name, $slug, $icon, $desc, $url, $wpOrgURL = false)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->slug = $slug;
|
||||
$this->icon = $icon;
|
||||
$this->desc = $desc;
|
||||
$this->url = $url;
|
||||
$this->wpOrgURL = $wpOrgURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns plugin slug
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSlug()
|
||||
{
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is plugin active
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
return $this->isInstalled() && is_plugin_active($this->slug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is plugin installed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInstalled()
|
||||
{
|
||||
static $installedSlugs = null;
|
||||
if ($installedSlugs === null) {
|
||||
if (!function_exists('get_plugins')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
$installedSlugs = array_keys(get_plugins());
|
||||
}
|
||||
return in_array($this->slug, $installedSlugs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks of the WP and PHP version requirments pass
|
||||
*
|
||||
* @return bool True if checks pass, false otherwise
|
||||
*/
|
||||
public function checkRequirments()
|
||||
{
|
||||
global $wp_version;
|
||||
|
||||
if (($reqs = $this->getRequirments()) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return version_compare($wp_version, $reqs['wp_version'], '>=') &&
|
||||
version_compare(PHP_VERSION, $reqs['php_version'], '>=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the requirments either from the cache (wp_options) or from the remote
|
||||
* API and updates the cache.
|
||||
*
|
||||
* @return array{last_updated:int,wp_version:string,php_version:string}|false The data or false of failure
|
||||
*/
|
||||
private function getRequirments()
|
||||
{
|
||||
if (($data = ExpireOptions::get($this->getApiSlug())) === false) {
|
||||
if (($data = $this->getRemoteRequirments()) !== false) {
|
||||
ExpireOptions::set($this->getApiSlug(), $data, 2 * WEEK_IN_SECONDS);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the slug that should be used with the API calls
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getApiSlug()
|
||||
{
|
||||
return dirname($this->getSlug());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the PHP and WP version requirments of a plugin from the WP API.
|
||||
*
|
||||
* @return array{wp_version:string,php_version:string}|false The data or false on failure
|
||||
*/
|
||||
private function getRemoteRequirments()
|
||||
{
|
||||
if (!function_exists('plugins_api')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
}
|
||||
|
||||
$response = plugins_api('plugin_information', [
|
||||
'slug' => $this->getApiSlug(),
|
||||
'fields' => self::PLUGIN_API_FIELDS
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'wp_version' => $response->requires,
|
||||
'php_version' => $response->requires_php
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pro version of plugin if available
|
||||
*
|
||||
* @return ExtraItem|null
|
||||
*/
|
||||
public function getPro()
|
||||
{
|
||||
return $this->pro;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set pro plugin
|
||||
*
|
||||
* @param string $name plugin name
|
||||
* @param string $slug plugin slug
|
||||
* @param string $icon url to plugin icon
|
||||
* @param string $desc plugin description
|
||||
* @param string $url plugin url
|
||||
* @param string|bool $wpOrgURL plugin url on wordpress.org
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPro($name, $slug, $icon, $desc, $url, $wpOrgURL = false)
|
||||
{
|
||||
$this->pro = new self($name, $slug, $icon, $desc, $url, $wpOrgURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to skip lite version of plugin because it is installed and pro version is available
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function skipLite()
|
||||
{
|
||||
return $this->pro !== null && $this->isActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of status constants (STATUS_ACTIVE, STATUS_INSTALLED, STATUS_UNINSALED)
|
||||
*
|
||||
* @return int return status constant
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
if ($this->isActive()) {
|
||||
return self::STATUS_ACTIVE;
|
||||
} elseif ($this->isInstalled()) {
|
||||
return self::STATUS_INSTALLED;
|
||||
} else {
|
||||
return self::STATUS_NOT_INSTALLED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Status text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatusText()
|
||||
{
|
||||
switch ($this->getStatus()) {
|
||||
case self::STATUS_ACTIVE:
|
||||
return __('Active', 'duplicator');
|
||||
case self::STATUS_INSTALLED:
|
||||
return __('Inactive', 'duplicator');
|
||||
case self::STATUS_NOT_INSTALLED:
|
||||
return __('Not Installed', 'duplicator');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of URL constants (URL_TYPE_GENERIC, URL_TYPE_ZIP)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getURLType()
|
||||
{
|
||||
if (SnapString::endsWith($this->url, '.zip')) {
|
||||
return self::URL_TYPE_ZIP;
|
||||
} else {
|
||||
return self::URL_TYPE_GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install this plugin
|
||||
*
|
||||
* @return bool true on success
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
if ($this->isInstalled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!SnapString::endsWith($this->url, '.zip')) {
|
||||
throw new \Exception('Invalid plugin url for installation');
|
||||
}
|
||||
|
||||
if (!current_user_can('install_plugins')) {
|
||||
throw new \Exception('User does not have permission to install plugins');
|
||||
}
|
||||
|
||||
if (!class_exists('Plugin_Upgrader')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
}
|
||||
wp_cache_flush();
|
||||
|
||||
$upgrader = new \Plugin_Upgrader(new \Automatic_Upgrader_Skin());
|
||||
if (!$upgrader->install($this->url)) {
|
||||
throw new \Exception('Failed to install plugin');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate this plugin
|
||||
*
|
||||
* @return bool true on success
|
||||
*/
|
||||
public function activate()
|
||||
{
|
||||
if ($this->isActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_null(activate_plugin($this->slug))) {
|
||||
throw new \Exception('Failed to activate plugin');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\ExtraPlugins;
|
||||
|
||||
final class ExtraPluginsMng
|
||||
{
|
||||
/** @var ?self */
|
||||
private static $instance = null;
|
||||
|
||||
/** @var array<string, ExtraItem> key slug item */
|
||||
protected $plugins = array();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton constructor
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
$this->plugins = self::getInitList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute callback for each plugin
|
||||
*
|
||||
* @param callable $callback callback function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function foreachCallback($callback)
|
||||
{
|
||||
if (!is_callable($callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->plugins as $plugin) {
|
||||
call_user_func($callback, $plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all plugins
|
||||
*
|
||||
* @return ExtraItem[] All plugin items
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
return $this->plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns plugin by slug
|
||||
*
|
||||
* @param string $slug plugin slug
|
||||
*
|
||||
* @return false|ExtraItem plugin item or false if not found
|
||||
*/
|
||||
public function getBySlug($slug)
|
||||
{
|
||||
if (strlen($slug) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($this->plugins[$slug])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (isset($this->plugins[$slug]) ? $this->plugins[$slug] : false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install plugin slug
|
||||
*
|
||||
* @param string $slug plugin slug
|
||||
* @param string $message message
|
||||
*
|
||||
* @return bool true if plugin installed and activated or false on failure
|
||||
*/
|
||||
public function install($slug, &$message = '')
|
||||
{
|
||||
if (strlen($slug) === 0) {
|
||||
$message = __('Plugin slug is empty', 'duplicator');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($plugin = $this->getBySlug($slug)) == false) {
|
||||
$message = __('Plugin not found', 'duplicator');
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = true;
|
||||
ob_start();
|
||||
if ($plugin->install() == false) {
|
||||
$result = false;
|
||||
} elseif ($plugin->activate() == false) {
|
||||
$result = false;
|
||||
}
|
||||
$message = ob_get_clean();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init addon plugins
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function getInitList()
|
||||
{
|
||||
$result = array();
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('OptinMonster', 'duplicator'),
|
||||
'optinmonster/optin-monster-wp-api.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-om.png',
|
||||
__('Instantly get more subscribers, leads, and sales with the #1 conversion optimization toolkit. Create ' .
|
||||
'high converting popups, announcement bars, spin a wheel, and more with smart targeting and personalization.', 'duplicator'),
|
||||
'https://downloads.wordpress.org/plugin/optinmonster.zip',
|
||||
'https://wordpress.org/plugins/optinmonster/'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('MonsterInsights', 'duplicator'),
|
||||
'google-analytics-for-wordpress/googleanalytics.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-mi.png',
|
||||
__(
|
||||
'The leading WordPress analytics plugin that shows you how people find and use your website, so you can ' .
|
||||
'make data driven decisions to grow your business. Properly set up Google Analytics without writing code.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.zip',
|
||||
'https://wordpress.org/plugins/google-analytics-for-wordpress/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('MonsterInsights Pro', 'duplicator'),
|
||||
'google-analytics-premium/googleanalytics-premium.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-mi.png',
|
||||
__(
|
||||
'The leading WordPress analytics plugin that shows you how people find and use your website, so you ' .
|
||||
'can make data driven decisions to grow your business. Properly set up Google Analytics without writing code.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://www.monsterinsights.com/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('WPForms', 'duplicator'),
|
||||
'wpforms-lite/wpforms.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-wpforms.png',
|
||||
__(
|
||||
'The best drag & drop WordPress form builder. Easily create beautiful contact forms, surveys, payment ' .
|
||||
'forms, and more with our 100+ form templates. Trusted by over 4 million websites as the best forms plugin.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/wpforms-lite.zip',
|
||||
'https://wordpress.org/plugins/wpforms-lite/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('WPForms Pro', 'duplicator'),
|
||||
'wpforms/wpforms.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-wpforms.png',
|
||||
__(
|
||||
'The easiest drag & drop WordPress form builder plugin to create beautiful contact forms, subscription ' .
|
||||
'forms, payment forms, and more in minutes. No coding skills required.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://wpforms.com/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('WP Mail SMTP', 'duplicator'),
|
||||
'wp-mail-smtp/wp_mail_smtp.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-smtp.png',
|
||||
__(
|
||||
'Improve your WordPress email deliverability and make sure that your website emails reach user\'s inbox ' .
|
||||
'with the #1 SMTP plugin for WordPress. Over 3 million websites use it to fix WordPress email issues.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/wp-mail-smtp.zip',
|
||||
'https://wordpress.org/plugins/wp-mail-smtp/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('WP Mail SMTP Pro', 'duplicator'),
|
||||
'wp-mail-smtp-pro/wp_mail_smtp.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-smtp.png',
|
||||
__(
|
||||
'Improve your WordPress email deliverability and make sure that your website emails reach user\'s inbox ' .
|
||||
'with the #1 SMTP plugin for WordPress. Over 3 million websites use it to fix WordPress email issues.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://wpmailsmtp.com/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('AIOSEO', 'duplicator'),
|
||||
'all-in-one-seo-pack/all_in_one_seo_pack.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-aioseo.png',
|
||||
__(
|
||||
'The original WordPress SEO plugin and toolkit that improves your website\'s search rankings. Comes with ' .
|
||||
'all the SEO features like Local SEO, WooCommerce SEO, sitemaps, SEO optimizer, schema, and more.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/all-in-one-seo-pack.zip',
|
||||
'https://wordpress.org/plugins/all-in-one-seo-pack/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('AIOSEO Pro', 'duplicator'),
|
||||
'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-aioseo.png',
|
||||
__(
|
||||
'The original WordPress SEO plugin and toolkit that improves your website\'s search rankings. Comes ' .
|
||||
'with all the SEO features like Local SEO, WooCommerce SEO, sitemaps, SEO optimizer, schema, and more.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://aioseo.com/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('SeedProd', 'duplicator'),
|
||||
'coming-soon/coming-soon.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-seedprod.png',
|
||||
__('The best WordPress coming soon page plugin to create a beautiful coming soon page, maintenance mode page, ' .
|
||||
'or landing page. No coding skills required.', 'duplicator'),
|
||||
'https://downloads.wordpress.org/plugin/coming-soon.zip',
|
||||
'https://wordpress.org/plugins/coming-soon/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('SeedProd Pro', 'duplicator'),
|
||||
'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-seedprod.png',
|
||||
__('The best WordPress coming soon page plugin to create a beautiful coming soon page, maintenance mode ' .
|
||||
'page, or landing page. No coding skills required.', 'duplicator'),
|
||||
'https://www.seedprod.com/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('RafflePress', 'duplicator'),
|
||||
'rafflepress/rafflepress.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-rp.png',
|
||||
__(
|
||||
'Turn your website visitors into brand ambassadors! Easily grow your email list, website traffic, and social ' .
|
||||
'media followers with the most powerful giveaways & contests plugin for WordPress.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/rafflepress.zip',
|
||||
'https://wordpress.org/plugins/rafflepress/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('RafflePress Pro', 'duplicator'),
|
||||
'rafflepress-pro/rafflepress-pro.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-rp.png',
|
||||
__(
|
||||
'Turn your website visitors into brand ambassadors! Easily grow your email list, website traffic, and ' .
|
||||
'social media followers with the most powerful giveaways & contests plugin for WordPress.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://rafflepress.com/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('PushEngage', 'duplicator'),
|
||||
'pushengage/main.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-pushengage.png',
|
||||
__(
|
||||
'Connect with your visitors after they leave your website with the leading web push notification software. ' .
|
||||
'Over 10,000+ businesses worldwide use PushEngage to send 9 billion notifications each month.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/pushengage.zip',
|
||||
'https://wordpress.org/plugins/pushengage/'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('Smash Balloon Instagram Feeds', 'duplicator'),
|
||||
'instagram-feed/instagram-feed.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sb-instagram.png',
|
||||
__(
|
||||
'Easily display Instagram content on your WordPress site without writing any code. Comes with multiple templates, ' .
|
||||
'ability to show content from multiple accounts, hashtags, and more. Trusted by 1 million websites.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/instagram-feed.zip',
|
||||
'https://wordpress.org/plugins/instagram-feed/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('Smash Balloon Instagram Feeds Pro', 'duplicator'),
|
||||
'instagram-feed-pro/instagram-feed.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sb-instagram.png',
|
||||
__(
|
||||
'Easily display Instagram content on your WordPress site without writing any code. Comes with multiple ' .
|
||||
'templates, ability to show content from multiple accounts, hashtags, and more. Trusted by 1 million websites.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://smashballoon.com/instagram-feed/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('Smash Balloon Facebook Feeds', 'duplicator'),
|
||||
'custom-facebook-feed/custom-facebook-feed.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sb-fb.png',
|
||||
__(
|
||||
'Easily display Facebook content on your WordPress site without writing any code. Comes with multiple templates, ' .
|
||||
'ability to embed albums, group content, reviews, live videos, comments, and reactions.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/custom-facebook-feed.zip',
|
||||
'https://wordpress.org/plugins/custom-facebook-feed/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('Smash Balloon Facebook Feeds Pro', 'duplicator'),
|
||||
'custom-facebook-feed-pro/custom-facebook-feed.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sb-fb.png',
|
||||
__(
|
||||
'Easily display Facebook content on your WordPress site without writing any code. Comes with multiple templates, ' .
|
||||
'ability to embed albums, group content, reviews, live videos, comments, and reactions.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://smashballoon.com/custom-facebook-feed/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('Smash Balloon Twitter Feeds', 'duplicator'),
|
||||
'custom-twitter-feeds/custom-twitter-feed.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sb-twitter.png',
|
||||
__(
|
||||
'Easily display Twitter content in WordPress without writing any code. Comes with multiple layouts, ability ' .
|
||||
'to combine multiple Twitter feeds, Twitter card support, tweet moderation, and more.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/custom-twitter-feeds.zip',
|
||||
'https://wordpress.org/plugins/custom-twitter-feeds/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('Smash Balloon Twitter Feeds Pro', 'duplicator'),
|
||||
'custom-twitter-feeds-pro/custom-twitter-feeds.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sb-twitter.png',
|
||||
__(
|
||||
'Easily display Twitter content in WordPress without writing any code. Comes with multiple layouts, ' .
|
||||
'ability to combine multiple Twitter feeds, Twitter card support, tweet moderation, and more.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://smashballoon.com/custom-twitter-feeds/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('Smash Balloon YouTube Feeds', 'duplicator'),
|
||||
'feeds-for-youtube/youtube-feed.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sb-youtube.png',
|
||||
__(
|
||||
'Easily display YouTube videos on your WordPress site without writing any code. Comes with multiple layouts, ' .
|
||||
'ability to embed live streams, video filtering, ability to combine multiple channel videos, and more.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/feeds-for-youtube.zip',
|
||||
'https://wordpress.org/plugins/feeds-for-youtube/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('Smash Balloon YouTube Feeds Pro', 'duplicator'),
|
||||
'youtube-feed-pro/youtube-feed.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sb-youtube.png',
|
||||
__(
|
||||
'Easily display YouTube videos on your WordPress site without writing any code. Comes with multiple ' .
|
||||
'layouts, ability to embed live streams, video filtering, ability to combine multiple channel videos, and more.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://smashballoon.com/youtube-feed/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('TrustPulse', 'duplicator'),
|
||||
'trustpulse-api/trustpulse.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-trustpulse.png',
|
||||
__(
|
||||
'Boost your sales and conversions by up to 15% with real-time social proof notifications. TrustPulse helps ' .
|
||||
'you show live user activity and purchases to help convince other users to purchase.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/trustpulse-api.zip',
|
||||
'https://wordpress.org/plugins/trustpulse-api/'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('SearchWP', 'duplicator'),
|
||||
'searchwp/index.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-searchwp.png',
|
||||
__(
|
||||
'The most advanced WordPress search plugin. Customize your WordPress search algorithm, reorder search results, ' .
|
||||
'track search metrics, and everything you need to leverage search to grow your business.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://searchwp.com/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('AffiliateWP', 'duplicator'),
|
||||
'affiliate-wp/affiliate-wp.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-affwp.png',
|
||||
__(
|
||||
'The #1 affiliate management plugin for WordPress. Easily create an affiliate program for your eCommerce ' .
|
||||
'store or membership site within minutes and start growing your sales with the power of referral marketing.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://affiliatewp.com/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator',
|
||||
false
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('WP Simple Pay', 'duplicator'),
|
||||
'stripe/stripe-checkout.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-wp-simple-pay.png',
|
||||
__(
|
||||
'The #1 Stripe payments plugin for WordPress. Start accepting one-time and recurring payments on your ' .
|
||||
'WordPress site without setting up a shopping cart. No code required.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/stripe.zip',
|
||||
'https://wordpress.org/plugins/stripe/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('WP Simple Pay Pro', 'duplicator'),
|
||||
'wp-simple-pay-pro-3/simple-pay.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-wp-simple-pay.png',
|
||||
__(
|
||||
'The #1 Stripe payments plugin for WordPress. Start accepting one-time and recurring payments on your ' .
|
||||
'WordPress site without setting up a shopping cart. No code required.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://wpsimplepay.com/lite-upgrade/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('Easy Digital Downloads', 'duplicator'),
|
||||
'easy-digital-downloads/easy-digital-downloads.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-edd.png',
|
||||
__('The best WordPress eCommerce plugin for selling digital downloads. Start selling eBooks, software, music, ' .
|
||||
'digital art, and more within minutes. Accept payments, manage subscriptions, advanced access control, and more.', 'duplicator'),
|
||||
'https://downloads.wordpress.org/plugin/easy-digital-downloads.zip',
|
||||
'https://wordpress.org/plugins/easy-digital-downloads/'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('Sugar Calendar', 'duplicator'),
|
||||
'sugar-calendar-lite/sugar-calendar-lite.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sugarcalendar.png',
|
||||
__('A simple & powerful event calendar plugin for WordPress that comes with all the event management features ' .
|
||||
'including payments, scheduling, timezones, ticketing, recurring events, and more.', 'duplicator'),
|
||||
'https://downloads.wordpress.org/plugin/sugar-calendar-lite.zip',
|
||||
'https://wordpress.org/plugins/sugar-calendar-lite/'
|
||||
);
|
||||
$item->setPro(
|
||||
__('Sugar Calendar Pro', 'duplicator'),
|
||||
'sugar-calendar/sugar-calendar.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-sugarcalendar.png',
|
||||
__('A simple & powerful event calendar plugin for WordPress that comes with all the event management features ' .
|
||||
'including payments, scheduling, timezones, ticketing, recurring events, and more.', 'duplicator'),
|
||||
'https://sugarcalendar.com/?utm_source=duplicatorplugin&utm_medium=link&utm_campaign=About%20Duplicator'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('WPCode', 'duplicator'),
|
||||
'insert-headers-and-footers/ihaf.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-wpcode.png',
|
||||
__('Future proof your WordPress customizations with the most popular code snippet management plugin for WordPress. ' .
|
||||
'Trusted by over 1,500,000+ websites for easily adding code to WordPress right from the admin area.', 'duplicator'),
|
||||
'https://downloads.wordpress.org/plugin/insert-headers-and-footers.zip',
|
||||
'https://wordpress.org/plugins/insert-headers-and-footers/'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('Search & Replace Everything', 'duplicator'),
|
||||
'search-replace-wpcode/wsrw.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-search-and-replace.png',
|
||||
__('Efficiently manage your website’s content directly from the WordPress admin with Search & Replace Everything by WPCode. ' .
|
||||
'This tool is essential for site migrations, content updates, or any situation where batch text and image replacements are needed.', 'duplicator'),
|
||||
'https://downloads.wordpress.org/plugin/search-replace-wpcode.zip',
|
||||
'https://wordpress.org/plugins/search-replace-wpcode/'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('Uncanny Automator', 'duplicator'),
|
||||
'uncanny-automator/uncanny-automator.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/img/about/plugin-uncanny-automator.png',
|
||||
__('Uncanny Automator is the easiest and most powerful way to automate your WordPress site with no code. ' .
|
||||
'Build automations in minutes that connect your WordPress plugins, sites and apps together using billions of recipe combinations.', 'duplicator'),
|
||||
'https://downloads.wordpress.org/plugin/uncanny-automator.zip',
|
||||
'https://wordpress.org/plugins/uncanny-automator/'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
$item = new ExtraItem(
|
||||
__('Database Reset Pro', 'duplicator'),
|
||||
'db-reset-pro/db-reset-pro.php',
|
||||
DUPLICATOR_PLUGIN_URL . 'assets/css/images/db-reset-icon.png',
|
||||
__(
|
||||
'Database Reset Pro is the safest and simplest way to reset your WordPress database to its default state.
|
||||
Unlike reinstalling WordPress, this database reset plugin preserves your files, uploads,
|
||||
and admin credentials while giving you a fresh start in seconds.',
|
||||
'duplicator'
|
||||
),
|
||||
'https://downloads.wordpress.org/plugin/db-reset-pro.zip',
|
||||
'https://wordpress.org/plugins/db-reset-pro/'
|
||||
);
|
||||
|
||||
$result[$item->getSlug()] = $item;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\Help;
|
||||
|
||||
class Article
|
||||
{
|
||||
/** @var int The ID */
|
||||
private $id = -1;
|
||||
|
||||
/** @var string The title */
|
||||
private $title = '';
|
||||
|
||||
/** @var string Link to the article */
|
||||
private $link = '';
|
||||
|
||||
/** @var int[] Categoriy IDs */
|
||||
private $categories = [];
|
||||
|
||||
/** @var string[] The tags */
|
||||
private $tags = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $id The ID
|
||||
* @param string $title The title
|
||||
* @param string $link Link to the article
|
||||
* @param int[] $categories Categories
|
||||
* @param string[] $tags Tags
|
||||
*/
|
||||
public function __construct($id, $title, $link, $categories, $tags = array())
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->title = $title;
|
||||
$this->link = $link;
|
||||
$this->categories = $categories;
|
||||
$this->tags = $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Link
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLink()
|
||||
{
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Categories
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getCategories()
|
||||
{
|
||||
return $this->categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Tags
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTags()
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
114
html/wp-content/plugins/duplicator/src/Utils/Help/Category.php
Normal file
114
html/wp-content/plugins/duplicator/src/Utils/Help/Category.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\Help;
|
||||
|
||||
class Category
|
||||
{
|
||||
/** @var int The ID */
|
||||
private $id = -1;
|
||||
|
||||
/** @var string The name */
|
||||
private $name = '';
|
||||
|
||||
/** @var int Number of articles */
|
||||
private $articleCount = 0;
|
||||
|
||||
/** @var Category|null The parent */
|
||||
private $parent = null;
|
||||
|
||||
/** @var Category[] The children */
|
||||
private $children = [];
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $id The ID
|
||||
* @param string $name The name
|
||||
* @param int $articleCount Number of articles
|
||||
*/
|
||||
public function __construct($id, $name, $articleCount)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->articleCount = $articleCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Article Count
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getArticleCount()
|
||||
{
|
||||
return $this->articleCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Children
|
||||
*
|
||||
* @return Category[]
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a child
|
||||
*
|
||||
* @param Category $child The child
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addChild(Category $child)
|
||||
{
|
||||
if (isset($this->children[$child->getId()])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->children[$child->getId()] = $child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Parent
|
||||
*
|
||||
* @return Category|null
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Parent
|
||||
*
|
||||
* @param Category $parent The parent
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParent(Category $parent)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
}
|
||||
}
|
||||
413
html/wp-content/plugins/duplicator/src/Utils/Help/Help.php
Normal file
413
html/wp-content/plugins/duplicator/src/Utils/Help/Help.php
Normal file
@@ -0,0 +1,413 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\Help;
|
||||
|
||||
use DUP_LITE_Plugin_Upgrade;
|
||||
use DUP_Log;
|
||||
use DUP_Settings;
|
||||
use Duplicator\Controllers\HelpPageController;
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use Duplicator\Libs\Snap\SnapJson;
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Utils\ExpireOptions;
|
||||
|
||||
/*
|
||||
* Dynamic Help from site documentation
|
||||
*/
|
||||
class Help
|
||||
{
|
||||
/** @var string The doc article endpoint */
|
||||
const ARTICLE_ENDPOINT = 'https://www.duplicator.com/wp-json/wp/v2/ht-kb';
|
||||
|
||||
/** @var string The doc categories endpoint */
|
||||
const CATEGORY_ENDPOINT = 'https://www.duplicator.com/wp-json/wp/v2/ht-kb-category';
|
||||
|
||||
/** @var string The doc tags endpoint */
|
||||
const TAGS_ENDPOINT = 'https://www.duplicator.com/wp-json/wp/v2/ht-kb-tag';
|
||||
|
||||
/** @var int Maximum number of articles to load */
|
||||
const MAX_ARTICLES = 500;
|
||||
|
||||
/** @var int Maximum number of categories to load */
|
||||
const MAX_CATEGORY = 20;
|
||||
|
||||
/** @var int Maximum number of tags to load */
|
||||
const MAX_TAGS = 100;
|
||||
|
||||
/** @var int Per page limit */
|
||||
const PER_PAGE = 100;
|
||||
|
||||
/** @var string Cron hook */
|
||||
const DOCS_EXPIRE_OPT_KEY = 'duplicator_help_docs_expire';
|
||||
|
||||
/** @var Article[] The articles */
|
||||
private $articles = [];
|
||||
|
||||
/** @var Category[] The categories */
|
||||
private $categories = [];
|
||||
|
||||
/** @var array<int, string> The tags ID => slug */
|
||||
private $tags = [];
|
||||
|
||||
/** @var self The instance */
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
// Update data from API if cache is expired or does not exist
|
||||
if (
|
||||
!ExpireOptions::getUpdate(self::DOCS_EXPIRE_OPT_KEY, true, WEEK_IN_SECONDS) ||
|
||||
!$this->loadData()
|
||||
) {
|
||||
$this->updateData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance
|
||||
*
|
||||
* @return self The instance
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the help page URL with tag
|
||||
*
|
||||
* @return string The URL with tag
|
||||
*/
|
||||
public static function getHelpPageUrl()
|
||||
{
|
||||
return HelpPageController::getHelpLink() . '&tag=' . self::getCurrentPageTag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get articles by category
|
||||
*
|
||||
* @param int $categoryId The category ID
|
||||
*
|
||||
* @return Article[] The articles
|
||||
*/
|
||||
public function getArticlesByCategory($categoryId)
|
||||
{
|
||||
return array_filter($this->articles, function (Article $article) use ($categoryId) {
|
||||
return in_array($categoryId, $article->getCategories());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get articles by tag
|
||||
*
|
||||
* @param string $tag The tag
|
||||
*
|
||||
* @return Article[] The articles
|
||||
*/
|
||||
public function getArticlesByTag($tag)
|
||||
{
|
||||
if ($tag === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_filter($this->articles, function (Article $article) use ($tag) {
|
||||
return in_array($tag, $article->getTags());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top level categories.
|
||||
* E.g. categories without parents & with children or articles
|
||||
*
|
||||
* @return Category[] The categories
|
||||
*/
|
||||
public function getTopLevelCategories()
|
||||
{
|
||||
return array_filter($this->categories, function (Category $category) {
|
||||
return $category->getParent() === null && (count($category->getChildren()) > 0 || $category->getArticleCount() > 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from API
|
||||
*
|
||||
* @return array{articles: mixed[], categories: mixed[], tags: mixed[]}|array<mixed> The data
|
||||
*/
|
||||
private function getDataFromApi()
|
||||
{
|
||||
$categories = $this->fetchDataFromEndpoint(
|
||||
self::CATEGORY_ENDPOINT,
|
||||
self::MAX_CATEGORY,
|
||||
[
|
||||
'id',
|
||||
'name',
|
||||
'count',
|
||||
'parent',
|
||||
]
|
||||
);
|
||||
|
||||
$articles = $this->fetchDataFromEndpoint(
|
||||
self::ARTICLE_ENDPOINT,
|
||||
self::MAX_ARTICLES,
|
||||
[
|
||||
'id',
|
||||
'title',
|
||||
'link',
|
||||
'ht-kb-category',
|
||||
'ht-kb-tag',
|
||||
]
|
||||
);
|
||||
|
||||
$tags = $this->fetchDataFromEndpoint(
|
||||
self::TAGS_ENDPOINT,
|
||||
self::MAX_TAGS,
|
||||
[
|
||||
'id',
|
||||
'slug',
|
||||
]
|
||||
);
|
||||
|
||||
if ($categories === [] || $articles === [] || $tags === []) {
|
||||
DUP_Log::Trace('Failed to load from API. No data.');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'articles' => $articles,
|
||||
'categories' => $categories,
|
||||
'tags' => $tags,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load from API
|
||||
*
|
||||
* @param string $endpoint The endpoint
|
||||
* @param int $limit Maximum number of items to load
|
||||
* @param string[] $fields The fields to load
|
||||
*
|
||||
* @return array<mixed> The data
|
||||
*/
|
||||
private function fetchDataFromEndpoint($endpoint, $limit, $fields = [])
|
||||
{
|
||||
$result = [];
|
||||
$endpointUrl = $endpoint . '?per_page=' . self::PER_PAGE;
|
||||
if (count($fields) > 0) {
|
||||
$endpointUrl .= '&_fields[]=' . implode('&_fields[]=', $fields);
|
||||
}
|
||||
|
||||
$maxPages = ceil($limit / self::PER_PAGE);
|
||||
for ($i = 1; $i <= $maxPages; $i++) {
|
||||
$endpointUrl .= '&page=' . $i;
|
||||
$response = wp_remote_get(
|
||||
$endpointUrl,
|
||||
['timeout' => 15]
|
||||
);
|
||||
if (is_wp_error($response)) {
|
||||
DUP_Log::Trace("Failed to load from API: {$endpointUrl}");
|
||||
DUP_Log::Trace($response->get_error_message());
|
||||
return [];
|
||||
}
|
||||
|
||||
$code = wp_remote_retrieve_response_code($response);
|
||||
if ($code !== 200) {
|
||||
DUP_Log::Trace("Failed to load from API: {$endpointUrl}, code: {$code}");
|
||||
return [];
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
if (($data = json_decode($body, true)) === null) {
|
||||
DUP_Log::Trace("Failed to decode response: {$body}");
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = array_merge($result, $data);
|
||||
$totalPages = wp_remote_retrieve_header($response, 'x-wp-totalpages');
|
||||
if ($totalPages === '' || $i >= (int) $totalPages) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$result = array_combine(array_column($result, 'id'), $result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current page tag
|
||||
*
|
||||
* @return string The tag
|
||||
*/
|
||||
private static function getCurrentPageTag()
|
||||
{
|
||||
if (!isset($_GET['page'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$page = $_GET['page'];
|
||||
$tab = isset($_GET['tab']) ? $_GET['tab'] : '';
|
||||
$innerPage = isset($_GET['inner_page']) ? $_GET['inner_page'] : '';
|
||||
|
||||
switch ($page) {
|
||||
case ControllersManager::PACKAGES_SUBMENU_SLUG:
|
||||
if ($innerPage === 'new1') {
|
||||
return 'backup_step_1';
|
||||
} elseif ($tab === 'new2') {
|
||||
return 'backup_step_2';
|
||||
} elseif ($tab === 'new3') {
|
||||
return 'backup_step_3';
|
||||
}
|
||||
|
||||
return 'backups';
|
||||
case ControllersManager::IMPORT_SUBMENU_SLUG:
|
||||
return 'import';
|
||||
case ControllersManager::SCHEDULES_SUBMENU_SLUG:
|
||||
return 'schedules';
|
||||
case ControllersManager::STORAGE_SUBMENU_SLUG:
|
||||
return 'storages';
|
||||
case ControllersManager::TOOLS_SUBMENU_SLUG:
|
||||
if ($tab === 'templates') {
|
||||
return 'templates';
|
||||
} elseif ($tab === 'recovery') {
|
||||
return 'recovery';
|
||||
}
|
||||
|
||||
return 'tools';
|
||||
case ControllersManager::SETTINGS_SUBMENU_SLUG:
|
||||
return 'settings';
|
||||
default:
|
||||
DUP_Log::Trace("No tag for page.");
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache path
|
||||
*
|
||||
* @return string The cache path
|
||||
*/
|
||||
private static function getCacheFilePath()
|
||||
{
|
||||
$installInfo = DUP_LITE_Plugin_Upgrade::getInstallInfo();
|
||||
return DUP_Settings::getSsdirPath() . '/cache_' . md5($installInfo['time']) . '/duplicator_help_cache.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set from cache data
|
||||
*
|
||||
* @param array{articles: mixed[], categories: mixed[], tags: mixed[]} $data The data
|
||||
*
|
||||
* @return bool True if set
|
||||
*/
|
||||
private function setFromArray($data)
|
||||
{
|
||||
if (!isset($data['articles']) || !isset($data['categories']) || !isset($data['tags'])) {
|
||||
DUP_Log::Trace("Invalid data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($data['tags'] as $tag) {
|
||||
$this->tags[$tag['id']] = $tag['slug'];
|
||||
}
|
||||
|
||||
foreach ($data['categories'] as $category) {
|
||||
$this->categories[$category['id']] = new Category(
|
||||
$category['id'],
|
||||
$category['name'],
|
||||
$category['count']
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->categories as $category) {
|
||||
if (
|
||||
($parentId = $data['categories'][$category->getId()]['parent']) === 0 ||
|
||||
!isset($this->categories[$parentId])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->categories[$parentId]->addChild($category);
|
||||
$category->setParent($this->categories[$parentId]);
|
||||
}
|
||||
|
||||
foreach ($data['articles'] as $article) {
|
||||
$this->articles[$article['id']] = new Article(
|
||||
$article['id'],
|
||||
$article['title']['rendered'],
|
||||
$article['link'],
|
||||
$article['ht-kb-category'],
|
||||
array_map(function ($tagId) {
|
||||
return $this->tags[$tagId];
|
||||
}, $article['ht-kb-tag'])
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from cache
|
||||
*
|
||||
* @return bool True if loaded
|
||||
*/
|
||||
private function loadData()
|
||||
{
|
||||
if (!file_exists(self::getCacheFilePath())) {
|
||||
DUP_Log::Trace("Cache file does not exist: " . self::getCacheFilePath());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($contents = file_get_contents(self::getCacheFilePath())) === false) {
|
||||
DUP_Log::Trace("Failed to read cache file: " . self::getCacheFilePath());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($data = json_decode($contents, true)) === null) {
|
||||
DUP_Log::Trace("Failed to decode cache file: " . self::getCacheFilePath());
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->setFromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save to cache
|
||||
*
|
||||
* @return bool True if saved
|
||||
*/
|
||||
public function updateData()
|
||||
{
|
||||
if (($data = $this->getDataFromApi()) === []) {
|
||||
DUP_Log::Trace("Failed to load data from API.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$cachePath = self::getCacheFilePath();
|
||||
$cacheDir = dirname($cachePath);
|
||||
if (!file_exists($cacheDir) && !SnapIO::mkdir($cacheDir, 0755, true)) {
|
||||
DUP_Log::Trace("Failed to create cache directory: {$cacheDir}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($encoded = SnapJson::jsonEncode($data)) === false) {
|
||||
DUP_Log::Trace("Failed to encode cache data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file_put_contents(self::getCacheFilePath(), $encoded) === false) {
|
||||
DUP_Log::Trace("Failed to write cache file: {$cachePath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->setFromArray($data);
|
||||
}
|
||||
}
|
||||
27
html/wp-content/plugins/duplicator/src/Utils/LinkManager.php
Normal file
27
html/wp-content/plugins/duplicator/src/Utils/LinkManager.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
*/
|
||||
|
||||
namespace Duplicator\Utils;
|
||||
|
||||
use Duplicator\Installer\Utils\InstallerLinkManager;
|
||||
|
||||
/**
|
||||
* Link manager class
|
||||
*/
|
||||
class LinkManager extends InstallerLinkManager
|
||||
{
|
||||
/**
|
||||
* @param string|string[] $paths The path to the article
|
||||
* @param string $medium The utm medium
|
||||
* @param string $content The utm content
|
||||
*
|
||||
* @return string The url with path and utm params
|
||||
*/
|
||||
protected static function buildUrl($paths, $medium, $content)
|
||||
{
|
||||
return apply_filters('duplicator_upsell_url_filter', parent::buildUrl($paths, $medium, $content));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\Support;
|
||||
|
||||
use DUP_Log;
|
||||
use DUP_Package;
|
||||
use DUP_Server;
|
||||
use DUP_Settings;
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
use Duplicator\Utils\ZipArchiveExtended;
|
||||
use Exception;
|
||||
|
||||
class SupportToolkit
|
||||
{
|
||||
const SUPPORT_TOOLKIT_BACKUP_NUMBER = 10;
|
||||
const SUPPORT_TOOLKIT_PREFIX = 'duplicator_support_toolkit_';
|
||||
|
||||
/**
|
||||
* Returns true if the diagnostic data can be downloaded
|
||||
*
|
||||
* @return bool true if diagnostic info can be downloaded
|
||||
*/
|
||||
public static function isAvailable()
|
||||
{
|
||||
return ZipArchiveExtended::isPhpZipAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the diagnostic data download URL if available,
|
||||
* empty string otherwise.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSupportToolkitDownloadUrl()
|
||||
{
|
||||
if (!self::isAvailable()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return admin_url('admin-ajax.php') . '?' . http_build_query([
|
||||
'action' => 'duplicator_download_support_toolkit',
|
||||
'nonce' => wp_create_nonce('duplicator_download_support_toolkit'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a support toolkit zip file
|
||||
*
|
||||
* @return string The path to the generated zip file
|
||||
*/
|
||||
public static function getToolkit()
|
||||
{
|
||||
$tempZipFilePath = DUP_Settings::getSsdirTmpPath() . '/' .
|
||||
self::SUPPORT_TOOLKIT_PREFIX . date('YmdHis') . '_' .
|
||||
SnapUtil::generatePassword(16, false, false) . '.zip';
|
||||
$zip = new ZipArchiveExtended($tempZipFilePath);
|
||||
|
||||
if ($zip->open() === false) {
|
||||
throw new Exception(__('Failed to create zip file', 'duplicator'));
|
||||
}
|
||||
|
||||
// Trace log
|
||||
if (DUP_Settings::Get('trace_log_enabled')) {
|
||||
$zip->addFile(DUP_Log::getTraceFilepath());
|
||||
}
|
||||
|
||||
// Debug log (if it exists)
|
||||
if (WP_DEBUG_LOG !== false) {
|
||||
if (is_bool(WP_DEBUG_LOG) && WP_DEBUG_LOG === true) {
|
||||
$zip->addFile(
|
||||
trailingslashit(wp_normalize_path(realpath(WP_CONTENT_DIR))) . 'debug.log',
|
||||
'',
|
||||
10 * MB_IN_BYTES
|
||||
);
|
||||
} elseif (is_string(WP_DEBUG_LOG) && strlen(WP_DEBUG_LOG) > 0) {
|
||||
//The path can be relative too so resolve via safepath
|
||||
$zip->addFile(
|
||||
SnapIO::safePath(WP_DEBUG_LOG, true),
|
||||
'',
|
||||
10 * MB_IN_BYTES
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//phpinfo (as html)
|
||||
$zip->addFileFromString('phpinfo.html', self::getPhpInfo());
|
||||
|
||||
//custom server settings info (as html)
|
||||
$zip->addFileFromString('serverinfo.txt', self::getPlainServerSettings());
|
||||
|
||||
//Last 10 backup build logs
|
||||
DUP_Package::by_status_callback(
|
||||
function (DUP_Package $package) use ($zip) {
|
||||
$file_path = DUP_Settings::getSsdirLogsPath() . "/" . $package->getLogFilename();
|
||||
$zip->addFile($file_path);
|
||||
},
|
||||
[],
|
||||
self::SUPPORT_TOOLKIT_BACKUP_NUMBER,
|
||||
0,
|
||||
'`id` DESC'
|
||||
);
|
||||
|
||||
return $tempZipFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of the "Server Settings" section in "Tools" > "General" in plain text format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getPlainServerSettings()
|
||||
{
|
||||
$result = '';
|
||||
|
||||
foreach (DUP_Server::getServerSettingsData() as $section) {
|
||||
$result .= $section['title'] . "\n";
|
||||
$result .= str_repeat('=', 50) . "\n";
|
||||
foreach ($section['settings'] as $data) {
|
||||
$result .= str_pad($data['logLabel'], 20, ' ', STR_PAD_RIGHT) . ' ' . $data['value'] . "\n";
|
||||
}
|
||||
$result .= "\n\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output of phpinfo as a string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getPhpInfo()
|
||||
{
|
||||
ob_start();
|
||||
SnapUtil::phpinfo();
|
||||
$phpInfo = ob_get_clean();
|
||||
|
||||
return $phpInfo === false ? '' : $phpInfo;
|
||||
}
|
||||
}
|
||||
69
html/wp-content/plugins/duplicator/src/Utils/Upsell.php
Normal file
69
html/wp-content/plugins/duplicator/src/Utils/Upsell.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
*/
|
||||
|
||||
namespace Duplicator\Utils;
|
||||
|
||||
/**
|
||||
* Upsell class, this class is used on plugin and installer
|
||||
*/
|
||||
class Upsell
|
||||
{
|
||||
/**
|
||||
* Get Pro features list
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getProFeatureList()
|
||||
{
|
||||
return array(
|
||||
__('Scheduled Backups', 'duplicator'),
|
||||
__('Recovery Points', 'duplicator'),
|
||||
__('Secure File Encryption', 'duplicator'),
|
||||
__('Server to Server Import', 'duplicator'),
|
||||
__('File & Database Table Filters', 'duplicator'),
|
||||
__('Cloud Storage - Google Drive', 'duplicator'),
|
||||
__('Cloud Storage - Amazon S3', 'duplicator'),
|
||||
__('Cloud Storage - DropBox', 'duplicator'),
|
||||
__('Cloud Storage - OneDrive', 'duplicator'),
|
||||
__('Cloud Storage - FTP/SFTP', 'duplicator'),
|
||||
__('Drag & Drop Installs', 'duplicator'),
|
||||
__('Larger Site Support', 'duplicator'),
|
||||
__('Multisite Network Support', 'duplicator'),
|
||||
__('Email Alerts', 'duplicator'),
|
||||
__('Advanced Backup Permissions', 'duplicator')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Pro callout features list
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getCalloutCTAFeatureList()
|
||||
{
|
||||
return array(
|
||||
__('Scheduled Backups', 'duplicator'),
|
||||
__('Recovery Points', 'duplicator'),
|
||||
__('Secure File Encryption', 'duplicator'),
|
||||
__('Server to Server Import', 'duplicator'),
|
||||
__('File & Database Table Filters', 'duplicator'),
|
||||
__('Cloud Storage', 'duplicator'),
|
||||
__('Smart Migration Wizard', 'duplicator'),
|
||||
__('Drag & Drop Installs', 'duplicator'),
|
||||
__('Streamlined Installer', 'duplicator'),
|
||||
__('Developer Hooks', 'duplicator'),
|
||||
__('Managed Hosting Support', 'duplicator'),
|
||||
__('Larger Site Support', 'duplicator'),
|
||||
__('Installer Branding', 'duplicator'),
|
||||
__('Migrate Duplicator Settings', 'duplicator'),
|
||||
__('Regenerate SALTS', 'duplicator'),
|
||||
__('Multisite Network', 'duplicator'),
|
||||
__('Email Alerts', 'duplicator'),
|
||||
__('Custom Search & Replace', 'duplicator'),
|
||||
__('Advanced Backup Permissions', 'duplicator')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\UsageStatistics;
|
||||
|
||||
use DUP_Log;
|
||||
use Duplicator\Libs\Snap\SnapLog;
|
||||
use Error;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
class CommStats
|
||||
{
|
||||
const API_VERSION = '1.0';
|
||||
const DEFAULT_REMOTE_HOST = 'https://connect.duplicator.com';
|
||||
const END_POINT_PLUGIN_STATS = '/api/ustats/addLiteStats';
|
||||
const END_POINT_DISABLE = '/api/ustats/disable';
|
||||
const END_POINT_INSTALLER = '/api/ustats/installer';
|
||||
|
||||
/**
|
||||
* Send plugin statistics
|
||||
*
|
||||
* @return bool true if data was sent successfully, false otherwise
|
||||
*/
|
||||
public static function pluginSend()
|
||||
{
|
||||
if (!StatsBootstrap::isTrackingAllowed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = PluginData::getInstance()->getDataToSend();
|
||||
|
||||
if (self::request(self::END_POINT_PLUGIN_STATS, $data)) {
|
||||
PluginData::getInstance()->updateLastSendTime();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabled usage tracking
|
||||
*
|
||||
* @return bool true if data was sent successfully, false otherwise
|
||||
*/
|
||||
public static function disableUsageTracking()
|
||||
{
|
||||
if (DUPLICATOR_USTATS_DISALLOW) { // @phpstan-ignore-line
|
||||
// Don't use StatsBootstrap::isTrackingAllowed beacause on disalbe usage tracking i necessary disable the tracking on server
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove usage tracking data on server
|
||||
$data = PluginData::getInstance()->getDisableDataToSend();
|
||||
return self::request(self::END_POINT_DISABLE, $data, 'Disable usage tracking error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent installer statistics
|
||||
*
|
||||
* @return bool true if data was sent successfully, false otherwise
|
||||
*/
|
||||
public static function installerSend()
|
||||
{
|
||||
if (!StatsBootstrap::isTrackingAllowed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = InstallerData::getInstance()->getDataToSend();
|
||||
return self::request(self::END_POINT_INSTALLER, $data, 'Installer usage tracking error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to usage tracking server
|
||||
*
|
||||
* @param string $endPoint end point
|
||||
* @param array<string, mixed> $data data to send
|
||||
* @param string $traceMessagePerefix trace message prefix
|
||||
*
|
||||
* @return bool true if data was sent successfully, false otherwise
|
||||
*/
|
||||
protected static function request($endPoint, $data, $traceMessagePerefix = 'Error sending usage tracking')
|
||||
{
|
||||
try {
|
||||
global $wp_version;
|
||||
|
||||
$agent_string = "WordPress/" . $wp_version;
|
||||
$postParams = array(
|
||||
'method' => 'POST',
|
||||
'timeout' => 10,
|
||||
'redirection' => 5,
|
||||
'sslverify' => false,
|
||||
'httpversion' => '1.1',
|
||||
//'blocking' => false,
|
||||
'user-agent' => $agent_string,
|
||||
'body' => $data,
|
||||
);
|
||||
|
||||
$url = self::getRemoteHost() . $endPoint . '/';
|
||||
$response = wp_remote_post($url, $postParams);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
/** @var WP_Error $response */
|
||||
DUP_Log::trace('URL Request: ' . $url);
|
||||
DUP_Log::trace($traceMessagePerefix . ' code: ' . $response->get_error_code());
|
||||
DUP_Log::trace('Error message: ' . $response->get_error_message());
|
||||
return false;
|
||||
} elseif ($response['response']['code'] < 200 || $response['response']['code'] >= 300) {
|
||||
DUP_Log::trace('URL Request: ' . $url);
|
||||
DUP_Log::trace($traceMessagePerefix . ' code: ' . $response['response']['code']);
|
||||
DUP_Log::trace('Error message: ' . $response['response']['message']);
|
||||
DUP_Log::traceObject('Data', $data);
|
||||
return false;
|
||||
} else {
|
||||
DUP_Log::trace('Usage tracking updated successfully');
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
DUP_Log::trace($traceMessagePerefix . ' trace msg: ' . $e->getMessage() . "\n" . SnapLog::getTextException($e, false));
|
||||
return false;
|
||||
} catch (Error $e) {
|
||||
DUP_Log::trace($traceMessagePerefix . ' trace msg: ' . $e->getMessage() . "\n" . SnapLog::getTextException($e, false));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote host
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getRemoteHost()
|
||||
{
|
||||
if (DUPLICATOR_CUSTOM_STATS_REMOTE_HOST != '') { // @phpstan-ignore-line
|
||||
return DUPLICATOR_CUSTOM_STATS_REMOTE_HOST;
|
||||
} else {
|
||||
return self::DEFAULT_REMOTE_HOST;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\UsageStatistics;
|
||||
|
||||
use DUP_DB;
|
||||
use Duplicator\Core\MigrationMng;
|
||||
use Duplicator\Libs\Snap\SnapDB;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
use wpdb;
|
||||
|
||||
class InstallerData
|
||||
{
|
||||
/**
|
||||
* @var ?self
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return usage tracking data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getDataToSend()
|
||||
{
|
||||
/** @var wpdb $wpdb */
|
||||
global $wpdb;
|
||||
|
||||
$data = (object) MigrationMng::getMigrationData();
|
||||
|
||||
$result = array(
|
||||
'api_version' => CommStats::API_VERSION,
|
||||
'plugin' => $data->plugin,
|
||||
'plugin_version' => $data->installerVersion,
|
||||
'install_type' => StatsUtil::getInstallType($data->installType),
|
||||
'logic_modes' => StatsUtil::getLogicModes($data->logicModes),
|
||||
'template' => StatsUtil::getTemplate($data->template),
|
||||
'wp_version' => get_bloginfo('version'),
|
||||
'db_engine' => SnapDB::getDBEngine($wpdb->dbh), // @phpstan-ignore-line
|
||||
'db_version' => DUP_DB::getVersion(),
|
||||
// SOURCE SITE INFO
|
||||
'source_phpv' => SnapUtil::getVersion($data->phpVersion, 3),
|
||||
// TARGET SITE INFO
|
||||
'target_phpv' => SnapUtil::getVersion(phpversion(), 3),
|
||||
// PACKAGE INFO
|
||||
'license_type' => StatsUtil::getLicenseType($data->licenseType),
|
||||
'archive_type' => $data->archiveType,
|
||||
'site_size_mb' => round(((int) $data->siteSize) / 1024 / 1024, 2),
|
||||
'site_num_files' => $data->siteNumFiles,
|
||||
'site_db_size_mb' => round(((int) $data->siteDbSize) / 1024 / 1024, 2),
|
||||
'site_db_num_tbl' => $data->siteDBNumTables,
|
||||
'components' => StatsUtil::getStatsComponents($data->components),
|
||||
);
|
||||
|
||||
$rules = array(
|
||||
'api_version' => 'string|max:7', // 1.0
|
||||
'plugin_version' => 'string|max:25',
|
||||
'wp_version' => 'string|max:25',
|
||||
'db_engine' => 'string|max:25',
|
||||
'db_version' => 'string|max:25',
|
||||
// SOURCE SERVER INFO
|
||||
'source_phpv' => 'string|max:25',
|
||||
// TARGET SERVER INFO
|
||||
'target_phpv' => 'string|max:25',
|
||||
);
|
||||
return StatsUtil::sanitizeFields($result, $rules);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\UsageStatistics;
|
||||
|
||||
use DUP_DB;
|
||||
use DUP_LITE_Plugin_Upgrade;
|
||||
use DUP_Log;
|
||||
use DUP_Package;
|
||||
use DUP_PackageStatus;
|
||||
use Duplicator\Libs\Snap\SnapDB;
|
||||
use Duplicator\Libs\Snap\SnapJson;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
use Duplicator\Libs\Snap\SnapWP;
|
||||
use ReflectionClass;
|
||||
use stdClass;
|
||||
use wpdb;
|
||||
|
||||
class PluginData
|
||||
{
|
||||
const PLUGIN_DATA_OPTION_KEY = 'duplicator_plugin_data_stats';
|
||||
const IDENTIFIER_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.,;=+&';
|
||||
|
||||
const PLUGIN_STATUS_ACTIVE = 'active';
|
||||
const PLUGIN_STATUS_INACTIVE = 'inactive';
|
||||
|
||||
/**
|
||||
* @var ?self
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $lastSendTime = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $identifier = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $plugin = 'dup-lite';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $pluginStatus = self::PLUGIN_STATUS_ACTIVE;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $buildCount = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $buildLastDate = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $buildFailedCount = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $buildFailedLastDate = 0;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $siteSizeMB = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $siteNumFiles = 0;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $siteDbSizeMB = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $siteDbNumTables = 0;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
if (($data = get_option(self::PLUGIN_DATA_OPTION_KEY)) !== false) {
|
||||
$data = json_decode($data, true);
|
||||
$reflect = new ReflectionClass(__CLASS__);
|
||||
$props = $reflect->getProperties();
|
||||
foreach ($props as $prop) {
|
||||
if (isset($data[$prop->getName()])) {
|
||||
$prop->setAccessible(true);
|
||||
$prop->setValue($this, $data[$prop->getName()]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->identifier = self::generateIdentifier();
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save plugin data
|
||||
*
|
||||
* @return bool True if data has been saved, false otherwise
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$values = get_object_vars($this);
|
||||
return update_option(self::PLUGIN_DATA_OPTION_KEY, SnapJson::jsonEncodePPrint($values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier()
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update from migrate data
|
||||
*
|
||||
* @param StdClass $data Migration data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateFromMigrateData(stdClass $data)
|
||||
{
|
||||
$save = false;
|
||||
if (
|
||||
isset($data->ustatIdentifier) &&
|
||||
strlen($data->ustatIdentifier) > 0 &&
|
||||
$data->ustatIdentifier !== $this->identifier
|
||||
) {
|
||||
$this->identifier = $data->ustatIdentifier;
|
||||
$save = true;
|
||||
}
|
||||
|
||||
return ($save ? $this->save() : true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return usage tracking data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getDataToSend()
|
||||
{
|
||||
$result = $this->getBasicInfos();
|
||||
$result = array_merge($result, $this->getPluginInfos());
|
||||
$result = array_merge($result, $this->getSiteInfos());
|
||||
$result = array_merge($result, $this->getManualPackageInfos());
|
||||
$result = array_merge($result, $this->getSettingsInfos());
|
||||
|
||||
$rules = array(
|
||||
'api_version' => 'string|max:7', // 1.0
|
||||
'identifier' => 'string|max:44',
|
||||
// BASIC INFO
|
||||
'plugin_version' => 'string|max:25',
|
||||
'php_version' => 'string|max:25',
|
||||
'wp_version' => 'string|max:25',
|
||||
// PLUGIN INFO
|
||||
'pinstall_version' => '?string|max:25',
|
||||
// SITE INFO
|
||||
'servertype' => 'string|max:25',
|
||||
'db_engine' => 'string|max:25',
|
||||
'db_version' => 'string|max:25',
|
||||
'timezoneoffset' => 'string|max:10',
|
||||
'locale' => 'string|max:10',
|
||||
'themename' => 'string|max:255',
|
||||
'themeversion' => 'string|max:25',
|
||||
);
|
||||
|
||||
return StatsUtil::sanitizeFields($result, $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disable tracking data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getDisableDataToSend()
|
||||
{
|
||||
$result = $this->getBasicInfos();
|
||||
|
||||
$rules = array(
|
||||
'api_version' => 'string|max:7', // 1.0
|
||||
'identifier' => 'string|max:44',
|
||||
// BASIC INFO
|
||||
'plugin_version' => 'string|max:25',
|
||||
'php_version' => 'string|max:25',
|
||||
'wp_version' => 'string|max:25',
|
||||
);
|
||||
|
||||
return StatsUtil::sanitizeFields($result, $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status
|
||||
*
|
||||
* @param string $status Status: active, inactive or uninstalled
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setStatus($status)
|
||||
{
|
||||
if ($this->pluginStatus === $status) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($status) {
|
||||
case self::PLUGIN_STATUS_ACTIVE:
|
||||
case self::PLUGIN_STATUS_INACTIVE:
|
||||
$this->pluginStatus = $status;
|
||||
$this->save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status
|
||||
*
|
||||
* @return string Enum: self::PLUGIN_STATUS_ACTIVE, self::PLUGIN_STATUS_INACTIVE or self::PLUGIN_STATUS_UNINSTALLED
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
return $this->pluginStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add paackage build count and date for manual and schedule build
|
||||
*
|
||||
* @param DUP_Package $package Package
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPackageBuild(DUP_Package $package)
|
||||
{
|
||||
if ($package->Status == DUP_PackageStatus::COMPLETE) {
|
||||
$this->buildCount++;
|
||||
$this->buildLastDate = time();
|
||||
} else {
|
||||
$this->buildFailedCount++;
|
||||
$this->buildFailedLastDate = time();
|
||||
}
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set site size
|
||||
*
|
||||
* @param int $size Site size in bytes
|
||||
* @param int $numFiles Number of files
|
||||
* @param int $dbSize Database size in bytes
|
||||
* @param int $numTables Number of tables
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setSiteSize($size, $numFiles, $dbSize, $numTables)
|
||||
{
|
||||
$this->siteSizeMB = round(((int) $size) / 1024 / 1024, 2);
|
||||
$this->siteNumFiles = (int) $numFiles;
|
||||
$this->siteDbSizeMB = round(((int) $dbSize) / 1024 / 1024, 2);
|
||||
$this->siteDbNumTables = (int) $numTables;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update last send time
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateLastSendTime()
|
||||
{
|
||||
$this->lastSendTime = time();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last send time
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastSendTime()
|
||||
{
|
||||
return $this->lastSendTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get basic infos
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function getBasicInfos()
|
||||
{
|
||||
return array(
|
||||
'api_version' => CommStats::API_VERSION,
|
||||
'identifier' => $this->identifier,
|
||||
'plugin' => $this->plugin,
|
||||
'plugin_status' => $this->pluginStatus,
|
||||
'plugin_version' => DUPLICATOR_VERSION,
|
||||
'php_version' => SnapUtil::getVersion(phpversion(), 3),
|
||||
'wp_version' => get_bloginfo('version'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return plugin infos
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function getPluginInfos()
|
||||
{
|
||||
if (($installInfo = DUP_LITE_Plugin_Upgrade::getInstallInfo()) === false) {
|
||||
$installInfo = array(
|
||||
'version' => null,
|
||||
'time' => null,
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'pinstall_date' => ($installInfo['time'] == null ? null : date('Y-m-d H:i:s', $installInfo['time'])),
|
||||
'pinstall_version' => ($installInfo['version'] == null ? null : $installInfo['version']),
|
||||
'license_type' => StatsUtil::getLicenseType(),
|
||||
'license_status' => StatsUtil::getLicenseStatus(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return site infos
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function getSiteInfos()
|
||||
{
|
||||
/** @var wpdb $wpdb */
|
||||
global $wpdb;
|
||||
|
||||
$theme_data = wp_get_theme();
|
||||
|
||||
return array(
|
||||
'servertype' => StatsUtil::getServerType(),
|
||||
'db_engine' => SnapDB::getDBEngine($wpdb->dbh), // @phpstan-ignore-line
|
||||
'db_version' => DUP_DB::getVersion(),
|
||||
'is_multisite' => is_multisite(),
|
||||
'sites_count' => count(SnapWP::getSitesIds()),
|
||||
'user_count' => SnapWp::getUsersCount(),
|
||||
'timezoneoffset' => get_option('gmt_offset'), /** @todo evaluate use wp or server timezone offset */
|
||||
'locale' => get_locale(),
|
||||
'am_family' => StatsUtil::getAmFamily(),
|
||||
'themename' => $theme_data->get('Name'),
|
||||
'themeversion' => $theme_data->get('Version'),
|
||||
'site_size_mb' => ($this->siteSizeMB == 0 ? null : $this->siteSizeMB),
|
||||
'site_num_files' => ($this->siteNumFiles == 0 ? null : $this->siteNumFiles),
|
||||
'site_db_size_mb' => ($this->siteDbSizeMB == 0 ? null : $this->siteDbSizeMB),
|
||||
'site_db_num_tbl' => ($this->siteDbNumTables == 0 ? null : $this->siteDbNumTables),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return manal package infos
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function getManualPackageInfos()
|
||||
{
|
||||
return array(
|
||||
'packages_build_count' => $this->buildCount,
|
||||
'packages_build_last_date' => ($this->buildLastDate == 0 ? null : date('Y-m-d H:i:s', $this->buildLastDate)),
|
||||
'packages_build_failed_count' => $this->buildFailedCount,
|
||||
'packages_build_failed_last_date' => ($this->buildFailedLastDate == 0 ? null : date('Y-m-d H:i:s', $this->buildFailedLastDate)),
|
||||
'packages_count' => DUP_Package::getNumCompletePackages(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return granular permissions infos
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function getSettingsInfos()
|
||||
{
|
||||
return array(
|
||||
'settings_archive_build_mode' => StatsUtil::getArchiveBuildMode(),
|
||||
'settings_db_build_mode' => StatsUtil::getDbBuildMode(),
|
||||
'settings_usage_enabled' => StatsBootstrap::isTrackingAllowed(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return unique identifier
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function generateIdentifier()
|
||||
{
|
||||
$maxRand = strlen(self::IDENTIFIER_CHARS) - 1;
|
||||
|
||||
$result = '';
|
||||
for ($i = 0; $i < 44; $i++) {
|
||||
$result .= substr(self::IDENTIFIER_CHARS, wp_rand(0, $maxRand), 1);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\UsageStatistics;
|
||||
|
||||
use DUP_Log;
|
||||
use DUP_Package;
|
||||
use DUP_PackageStatus;
|
||||
use DUP_Settings;
|
||||
use Duplicator\Utils\CronUtils;
|
||||
|
||||
/**
|
||||
* StatsBootstrap
|
||||
*/
|
||||
class StatsBootstrap
|
||||
{
|
||||
const USAGE_TRACKING_CRON_HOOK = 'duplicator_usage_tracking_cron';
|
||||
|
||||
/**
|
||||
* Init WordPress hooks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
add_action('duplicator_after_activation', array(__CLASS__, 'activationAction'));
|
||||
add_action('duplicator_after_deactivation', array(__CLASS__, 'deactivationAction'));
|
||||
add_action('duplicator_package_after_set_status', array(__CLASS__, 'addPackageBuild'), 10, 2);
|
||||
add_action('duplicator_after_scan_report', array(__CLASS__, 'addSiteSizes'), 10, 2);
|
||||
add_action('duplicator_usage_tracking_cron', array(__CLASS__, 'sendPluginStatCron'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Activation action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function activationAction()
|
||||
{
|
||||
// Set cron
|
||||
if (!wp_next_scheduled(self::USAGE_TRACKING_CRON_HOOK)) {
|
||||
$randomTracking = wp_rand(0, WEEK_IN_SECONDS);
|
||||
$timeToStart = strtotime('next sunday') + $randomTracking;
|
||||
wp_schedule_event($timeToStart, CronUtils::INTERVAL_WEEKLY, self::USAGE_TRACKING_CRON_HOOK);
|
||||
}
|
||||
|
||||
if (PluginData::getInstance()->getStatus() !== PluginData::PLUGIN_STATUS_ACTIVE) {
|
||||
PluginData::getInstance()->setStatus(PluginData::PLUGIN_STATUS_ACTIVE);
|
||||
CommStats::pluginSend();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivation action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function deactivationAction()
|
||||
{
|
||||
// Unschedule custom cron event for cleanup if it's scheduled
|
||||
if (wp_next_scheduled(self::USAGE_TRACKING_CRON_HOOK)) {
|
||||
$timestamp = wp_next_scheduled(self::USAGE_TRACKING_CRON_HOOK);
|
||||
wp_unschedule_event($timestamp, self::USAGE_TRACKING_CRON_HOOK);
|
||||
}
|
||||
|
||||
PluginData::getInstance()->setStatus(PluginData::PLUGIN_STATUS_INACTIVE);
|
||||
CommStats::pluginSend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add package build,
|
||||
* don't use PluginData::getInstance()->addPackageBuild() directly in hook to avoid useless init
|
||||
*
|
||||
* @param DUP_Package $package Package
|
||||
* @param int $status Status DUP_PRO_PackageStatus Enum
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addPackageBuild(DUP_Package $package, $status)
|
||||
{
|
||||
if ($status >= DUP_PackageStatus::CREATED && $status < DUP_PackageStatus::COMPLETE) {
|
||||
return;
|
||||
}
|
||||
PluginData::getInstance()->addPackageBuild($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add site size statistics
|
||||
*
|
||||
* @param DUP_Package $package Package
|
||||
* @param array<string, mixed> $report Scan report
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addSiteSizes(DUP_Package $package, $report)
|
||||
{
|
||||
if ($package->Archive->ExportOnlyDB) {
|
||||
return;
|
||||
}
|
||||
|
||||
PluginData::getInstance()->setSiteSize(
|
||||
$report['ARC']['USize'],
|
||||
$report['ARC']['UFullCount'],
|
||||
$report['DB']['RawSize'],
|
||||
$report['DB']['TableCount']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is tracking allowed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTrackingAllowed()
|
||||
{
|
||||
if (DUPLICATOR_USTATS_DISALLOW) { // @phpstan-ignore-line
|
||||
return false;
|
||||
}
|
||||
|
||||
return DUP_Settings::Get('usage_tracking', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send plugin statistics
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function sendPluginStatCron()
|
||||
{
|
||||
if (!self::isTrackingAllowed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DUP_Log::trace("CRON: Sending plugin statistics");
|
||||
CommStats::pluginSend();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Utils\UsageStatistics;
|
||||
|
||||
use DUP_Archive_Build_Mode;
|
||||
use DUP_DB;
|
||||
use DUP_Settings;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
use Duplicator\Libs\Snap\SnapWP;
|
||||
use Exception;
|
||||
|
||||
class StatsUtil
|
||||
{
|
||||
/**
|
||||
* Get server type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getServerType()
|
||||
{
|
||||
if (empty($_SERVER['SERVER_SOFTWARE'])) {
|
||||
return 'unknown';
|
||||
}
|
||||
return SnapUtil::sanitizeNSCharsNewlineTrim(wp_unslash($_SERVER['SERVER_SOFTWARE']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get db mode
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDbBuildMode()
|
||||
{
|
||||
switch (DUP_DB::getBuildMode()) {
|
||||
case DUP_DB::BUILD_MODE_MYSQLDUMP:
|
||||
return 'mysqldump';
|
||||
case DUP_DB::BUILD_MODE_PHP_SINGLE_THREAD:
|
||||
return 'php-single';
|
||||
default:
|
||||
throw new Exception('Unknown db build mode');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get archive mode
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getArchiveBuildMode()
|
||||
{
|
||||
if (DUP_Settings::Get('archive_build_mode') == DUP_Archive_Build_Mode::ZipArchive) {
|
||||
return 'zip-single';
|
||||
} else {
|
||||
return 'dup';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return license types
|
||||
*
|
||||
* @param ?int $type License type, if null will use current license type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLicenseType($type = null)
|
||||
{
|
||||
return 'unlicensed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return license status
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLicenseStatus()
|
||||
{
|
||||
return 'invalid';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get install type
|
||||
*
|
||||
* @param int $type Install type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getInstallType($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case -1:
|
||||
return 'single';
|
||||
case 4:
|
||||
return 'single_on_subdomain';
|
||||
case 5:
|
||||
return 'single_on_subfolder';
|
||||
case 8:
|
||||
return 'rbackup_single';
|
||||
default:
|
||||
return 'not_set';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stats components
|
||||
*
|
||||
* @param string[] $components Components
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getStatsComponents($components)
|
||||
{
|
||||
$result = array();
|
||||
foreach ($components as $component) {
|
||||
switch ($component) {
|
||||
case 'package_component_db':
|
||||
$result[] = 'db';
|
||||
break;
|
||||
case 'package_component_core':
|
||||
$result[] = 'core';
|
||||
break;
|
||||
case 'package_component_plugins':
|
||||
$result[] = 'plugins';
|
||||
break;
|
||||
case 'package_component_plugins_active':
|
||||
$result[] = 'plugins_active';
|
||||
break;
|
||||
case 'package_component_themes':
|
||||
$result[] = 'themes';
|
||||
break;
|
||||
case 'package_component_themes_active':
|
||||
$result[] = 'themes_active';
|
||||
break;
|
||||
case 'package_component_uploads':
|
||||
$result[] = 'uploads';
|
||||
break;
|
||||
case 'package_component_other':
|
||||
$result[] = 'other';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return implode(',', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get am family plugins
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getAmFamily()
|
||||
{
|
||||
$result = array();
|
||||
$result[] = 'dup-pro';
|
||||
if (SnapWP::isPluginInstalled('duplicator/duplicator.php')) {
|
||||
$result[] = 'dup-lite';
|
||||
}
|
||||
|
||||
return implode(',', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logic modes
|
||||
*
|
||||
* @param string[] $modes Logic modes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLogicModes($modes)
|
||||
{
|
||||
$result = array();
|
||||
foreach ($modes as $mode) {
|
||||
switch ($mode) {
|
||||
case 'CLASSIC':
|
||||
$result[] = 'CLASSIC';
|
||||
break;
|
||||
case 'OVERWRITE':
|
||||
$result[] = 'OVERWRITE';
|
||||
break;
|
||||
case 'RESTORE_BACKUP':
|
||||
$result[] = 'RESTORE';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return implode(',', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template
|
||||
*
|
||||
* @param string $template Template
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getTemplate($template)
|
||||
{
|
||||
switch ($template) {
|
||||
case 'base':
|
||||
return 'CLASSIC_BASE';
|
||||
case 'import-base':
|
||||
return 'IMPORT_BASE';
|
||||
case 'import-advanced':
|
||||
return 'IMPORT_ADV';
|
||||
case 'recovery':
|
||||
return 'RECOVERY';
|
||||
case 'default':
|
||||
default:
|
||||
return 'CLASSIC_ADV';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize fields with rule string
|
||||
* [nullable][type][|max:number]
|
||||
* - ?string|max:25
|
||||
* - int
|
||||
*
|
||||
* @param array<string, mixed> $data Data
|
||||
* @param array<string, string> $rules Rules
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function sanitizeFields($data, $rules)
|
||||
{
|
||||
foreach ($data as $key => $val) {
|
||||
if (!isset($rules[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matches = null;
|
||||
if (preg_match('/(\??)(int|float|bool|string)(?:\|max:(\d+))?/', $rules[$key], $matches) !== 1) {
|
||||
throw new Exception("Invalid sanitize rule: {$rules[$key]}");
|
||||
}
|
||||
|
||||
$nullable = $matches[1] === '?';
|
||||
$type = $matches[2];
|
||||
$max = isset($matches[3]) ? (int) $matches[3] : PHP_INT_MAX;
|
||||
|
||||
if ($nullable && $val === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'int':
|
||||
$data[$key] = (int) $val;
|
||||
break;
|
||||
case 'float':
|
||||
$data[$key] = (float) $val;
|
||||
break;
|
||||
case 'bool':
|
||||
$data[$key] = (bool) $val;
|
||||
break;
|
||||
case 'string':
|
||||
$data[$key] = substr((string) $val, 0, $max);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown sanitize rule: {$rules[$key]}");
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
*/
|
||||
|
||||
namespace Duplicator\Utils;
|
||||
|
||||
use Duplicator\Libs\Snap\SnapIO;
|
||||
use Duplicator\Libs\Snap\SnapLog;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
use Exception;
|
||||
use ZipArchive;
|
||||
|
||||
class ZipArchiveExtended
|
||||
{
|
||||
/** @var string */
|
||||
protected $archivePath = '';
|
||||
/** @var ZipArchive */
|
||||
protected $zipArchive = null;
|
||||
/** @var bool */
|
||||
protected $isOpened = false;
|
||||
/** @var bool */
|
||||
protected $compressed = false;
|
||||
/** @var bool */
|
||||
protected $encrypt = false;
|
||||
/** @var string */
|
||||
protected $password = '';
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param string $path zip archive path
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
if (!self::isPhpZipAvailable()) {
|
||||
throw new Exception('ZipArchive PHP module is not installed/enabled.');
|
||||
}
|
||||
if (file_exists($path) && (!is_file($path) || !is_writeable($path))) {
|
||||
throw new Exception('File ' . SnapLog::v2str($path) . 'exists but isn\'t valid');
|
||||
}
|
||||
|
||||
$this->archivePath = $path;
|
||||
$this->zipArchive = new ZipArchive();
|
||||
$this->setCompressed(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class destructor
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if class ZipArchvie is available
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPhpZipAvailable()
|
||||
{
|
||||
return SnapUtil::classExists(ZipArchive::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add full dir in archive
|
||||
*
|
||||
* @param string $dirPath dir path
|
||||
* @param string $archivePath local archive path
|
||||
*
|
||||
* @return bool TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function addDir($dirPath, $archivePath)
|
||||
{
|
||||
if (!is_dir($dirPath) || !is_readable($dirPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dirPath = SnapIO::safePathTrailingslashit($dirPath);
|
||||
$archivePath = SnapIO::safePathTrailingslashit($archivePath);
|
||||
$thisObj = $this;
|
||||
|
||||
return SnapIO::regexGlobCallback(
|
||||
$dirPath,
|
||||
function ($path) use ($dirPath, $archivePath, $thisObj) {
|
||||
$newPath = $archivePath . SnapIO::getRelativePath($path, $dirPath);
|
||||
|
||||
if (is_dir($path)) {
|
||||
$thisObj->addEmptyDir($newPath);
|
||||
} else {
|
||||
$thisObj->addFile($path, $newPath);
|
||||
}
|
||||
},
|
||||
array('recursive' => true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add empty dir on zip archive
|
||||
*
|
||||
* @param string $path archive dir to add
|
||||
*
|
||||
* @return bool TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function addEmptyDir($path)
|
||||
{
|
||||
return $this->zipArchive->addEmptyDir($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file on zip archive
|
||||
*
|
||||
* @param string $filepath file path
|
||||
* @param string $archivePath archive path, if empty use file name
|
||||
* @param int $maxSize max size of file, if the size is grater than this value the file will be truncated, 0 for no limit
|
||||
*
|
||||
* @return bool TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function addFile($filepath, $archivePath = '', $maxSize = 0)
|
||||
{
|
||||
if (!is_file($filepath) || !is_readable($filepath)) {
|
||||
return false;
|
||||
}
|
||||
if (strlen($archivePath) === 0) {
|
||||
$archivePath = basename($filepath);
|
||||
}
|
||||
if ($maxSize > 0 && filesize($filepath) > $maxSize) {
|
||||
if (($content = file_get_contents($filepath, false, null, 0, $maxSize)) === false) {
|
||||
return false;
|
||||
}
|
||||
$result = $this->zipArchive->addFromString($archivePath, $content);
|
||||
} else {
|
||||
$result = $this->zipArchive->addFile($filepath, $archivePath);
|
||||
}
|
||||
if ($result && $this->encrypt) {
|
||||
$this->zipArchive->setEncryptionName($archivePath, ZipArchive::EM_AES_256);
|
||||
}
|
||||
if ($result && !$this->compressed) {
|
||||
$this->zipArchive->setCompressionName($archivePath, ZipArchive::CM_STORE);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary file with the given content. The file will be deleted after the zip is created
|
||||
*
|
||||
* @param string $archivePath Filename in archive
|
||||
* @param string $content Content of the file
|
||||
* @param int $maxSize max size of file, if the size is grater than this value the file will be truncated, 0 for no limit
|
||||
*
|
||||
* @return bool returns true if the file was created successfully
|
||||
*/
|
||||
public function addFileFromString($archivePath, $content, $maxSize = 0)
|
||||
{
|
||||
if ($maxSize > 0 && strlen($content) > $maxSize) {
|
||||
$content = substr($content, 0, $maxSize);
|
||||
}
|
||||
$result = $this->zipArchive->addFromString($archivePath, $content);
|
||||
if ($result && $this->encrypt) {
|
||||
$this->zipArchive->setEncryptionName($archivePath, ZipArchive::EM_AES_256);
|
||||
}
|
||||
if ($result && !$this->compressed) {
|
||||
$this->zipArchive->setCompressionName($archivePath, ZipArchive::CM_STORE);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Zip archive, create it if don't exists
|
||||
*
|
||||
* @return bool|int Returns TRUE on success or the error code. See zip archive
|
||||
*/
|
||||
public function open()
|
||||
{
|
||||
if ($this->isOpened) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (($result = $this->zipArchive->open($this->archivePath, ZipArchive::CREATE)) === true) {
|
||||
$this->isOpened = true;
|
||||
if ($this->encrypt) {
|
||||
$this->zipArchive->setPassword($this->password);
|
||||
} else {
|
||||
$this->zipArchive->setPassword('');
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close zip archive
|
||||
*
|
||||
* @return bool True on success or false on failure.
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if (!$this->isOpened) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$result = false;
|
||||
|
||||
if (($result = $this->zipArchive->close()) === true) {
|
||||
$this->isOpened = false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get num files in zip archive
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getNumFiles()
|
||||
{
|
||||
$this->open();
|
||||
return $this->zipArchive->numFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of compressed\
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCompressed()
|
||||
{
|
||||
return $this->compressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Se compression if is available
|
||||
*
|
||||
* @param bool $compressed if true compress zip archive
|
||||
*
|
||||
* @return bool return compressd value
|
||||
*/
|
||||
public function setCompressed($compressed)
|
||||
{
|
||||
if (!method_exists($this->zipArchive, 'setCompressionName')) {
|
||||
// If don't exists setCompressionName the archive can't create uncrompressed
|
||||
$this->compressed = true;
|
||||
} else {
|
||||
$this->compressed = $compressed;
|
||||
}
|
||||
return $this->compressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of encrypt
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEncrypted()
|
||||
{
|
||||
return $this->encrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if ZipArchive encryption is available
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEncryptionAvaliable()
|
||||
{
|
||||
static $isEncryptAvailable = null;
|
||||
if ($isEncryptAvailable === null) {
|
||||
if (!self::isPhpZipAvailable()) {
|
||||
$isEncryptAvailable = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
$zipArchive = new ZipArchive();
|
||||
if (!method_exists($zipArchive, 'setEncryptionName')) {
|
||||
$isEncryptAvailable = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (version_compare(self::getLibzipVersion(), '1.2.0', '<')) {
|
||||
$isEncryptAvailable = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
$isEncryptAvailable = true;
|
||||
}
|
||||
|
||||
return $isEncryptAvailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get libzip version
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLibzipVersion()
|
||||
{
|
||||
static $zlibVersion = null;
|
||||
|
||||
if (is_null($zlibVersion)) {
|
||||
ob_start();
|
||||
SnapUtil::phpinfo(INFO_MODULES);
|
||||
$info = (string) ob_get_clean();
|
||||
|
||||
if (preg_match('/<td\s.*?>\s*(libzip.*\sver.+?)\s*<\/td>\s*<td\s.*?>\s*(.+?)\s*<\/td>/i', $info, $matches) !== 1) {
|
||||
$zlibVersion = "0";
|
||||
} else {
|
||||
$zlibVersion = $matches[2];
|
||||
}
|
||||
}
|
||||
|
||||
return $zlibVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set encryption
|
||||
*
|
||||
* @param bool $encrypt true if archvie must be encrypted
|
||||
* @param string $password password
|
||||
|
||||
* @return bool
|
||||
*/
|
||||
public function setEncrypt($encrypt, $password = '')
|
||||
{
|
||||
$this->encrypt = (self::isEncryptionAvaliable() ? $encrypt : false);
|
||||
|
||||
if ($this->encrypt) {
|
||||
$this->password = $password;
|
||||
} else {
|
||||
$this->password = '';
|
||||
}
|
||||
|
||||
if ($this->isOpened) {
|
||||
if ($this->encrypt) {
|
||||
$this->zipArchive->setPassword($this->password);
|
||||
} else {
|
||||
$this->zipArchive->setPassword('');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->encrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Files regex search and return zip file stat
|
||||
*
|
||||
* @param string $path Archive path
|
||||
* @param string $regex Regex to search
|
||||
* @param string $password Password if archive is encrypted or empty string
|
||||
*
|
||||
* @return false|array{name:string,index:int,crc:int,size:int,mtime:int,comp_size:int,comp_method:int}
|
||||
*/
|
||||
public static function searchRegex($path, $regex, $password = '')
|
||||
{
|
||||
if (!self::isPhpZipAvailable()) {
|
||||
throw new Exception(__('ZipArchive PHP module is not installed/enabled. The current Backup cannot be opened.', 'duplicator'));
|
||||
}
|
||||
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($path) !== true) {
|
||||
throw new Exception('Cannot open the ZipArchive file. Please see the online FAQ\'s for additional help.' . $path);
|
||||
}
|
||||
|
||||
if (strlen($password)) {
|
||||
$zip->setPassword($password);
|
||||
}
|
||||
|
||||
$result = false;
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
/** @var array{name:string,index:int,crc:int,size:int,mtime:int,comp_size:int,comp_method:int} */
|
||||
$stat = $zip->statIndex($i);
|
||||
$name = basename($stat['name']);
|
||||
if (preg_match($regex, $name) === 1) {
|
||||
$result = $stat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
548
html/wp-content/plugins/duplicator/src/Views/AdminNotices.php
Normal file
548
html/wp-content/plugins/duplicator/src/Views/AdminNotices.php
Normal file
@@ -0,0 +1,548 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Views;
|
||||
|
||||
use Closure;
|
||||
use DUP_Server;
|
||||
use Duplicator\Core\MigrationMng;
|
||||
use Duplicator\Utils\LinkManager;
|
||||
use Duplicator\Libs\Snap\SnapUtil;
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Core\Notifications\Notice;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
use Duplicator\Utils\Autoloader;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Admin Notices
|
||||
*/
|
||||
class AdminNotices
|
||||
{
|
||||
const OPTION_KEY_MIGRATION_SUCCESS_NOTICE = 'duplicator_migration_success';
|
||||
const OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL = 'duplicator_activate_plugins_after_installation';
|
||||
|
||||
//TEMPLATE VALUE: This is a just a simple example for setting up quick notices
|
||||
const OPTION_KEY_NEW_NOTICE_TEMPLATE = 'duplicator_new_template_notice';
|
||||
const OPTION_KEY_IS_ENABLE_NOTICE_DISMISSED = 'duplicator_is_enable_notice_dismissed';
|
||||
const OPTION_KEY_IS_MU_NOTICE_DISMISSED = 'duplicator_is_mu_notice_dismissed';
|
||||
|
||||
const GEN_INFO_NOTICE = 0;
|
||||
const GEN_SUCCESS_NOTICE = 1;
|
||||
const GEN_WARNING_NOTICE = 2;
|
||||
const GEN_ERROR_NOTICE = 3;
|
||||
|
||||
/**
|
||||
* init notice actions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
add_action('admin_init', array(__CLASS__, 'adminInit'));
|
||||
add_action('admin_enqueue_scripts', array(__CLASS__, 'unhookThirdPartyNotices'), 99999, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* init notice actions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function adminInit()
|
||||
{
|
||||
$notices = array();
|
||||
if (is_multisite()) {
|
||||
$noCapabilitiesNotice = is_super_admin() && !current_user_can('export');
|
||||
$notices[] = array(__CLASS__, 'multisiteNotice');
|
||||
} else {
|
||||
$noCapabilitiesNotice = in_array('administrator', $GLOBALS['current_user']->roles) && !current_user_can('export');
|
||||
}
|
||||
|
||||
if ($noCapabilitiesNotice) {
|
||||
$notices[] = array(__CLASS__, 'showNoExportCapabilityNotice');
|
||||
}
|
||||
|
||||
if (is_multisite()) {
|
||||
$displayNotices = is_super_admin() && current_user_can('export');
|
||||
} else {
|
||||
$displayNotices = current_user_can('export');
|
||||
}
|
||||
|
||||
if ($displayNotices) {
|
||||
$notices[] = array(__CLASS__, 'clearInstallerFilesAction'); // BEFORE MIGRATION SUCCESS NOTICE
|
||||
$notices[] = array(__CLASS__, 'migrationSuccessNotice');
|
||||
$notices[] = array(__CLASS__, 'installAutoDeactivatePlugins');
|
||||
$notices[] = array(__CLASS__, 'failedOneClickUpgradeNotice');
|
||||
}
|
||||
|
||||
$action = is_multisite() ? 'network_admin_notices' : 'admin_notices';
|
||||
foreach ($notices as $notice) {
|
||||
add_action($action, $notice);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all notices coming from other plugins
|
||||
*
|
||||
* @param string $hook Hook string
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function unhookThirdPartyNotices($hook)
|
||||
{
|
||||
if (!ControllersManager::isDuplicatorPage()) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wp_filter;
|
||||
$filterHooks = array('user_admin_notices', 'admin_notices', 'all_admin_notices', 'network_admin_notices');
|
||||
foreach ($filterHooks as $filterHook) {
|
||||
if (empty($wp_filter[$filterHook]->callbacks) || !is_array($wp_filter[$filterHook]->callbacks)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($wp_filter[$filterHook]->callbacks as $priority => $hooks) {
|
||||
foreach ($hooks as $name => $arr) {
|
||||
if (is_object($arr['function']) && $arr['function'] instanceof Closure) {
|
||||
unset($wp_filter[$filterHook]->callbacks[$priority][$name]);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!empty($arr['function'][0]) &&
|
||||
is_object($arr['function'][0]) &&
|
||||
strpos(get_class($arr['function'][0]), Autoloader::ROOT_NAMESPACE) === 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (!empty($name) && strpos($name, Autoloader::ROOT_NAMESPACE) !== 0) {
|
||||
unset($wp_filter[$filterHook]->callbacks[$priority][$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear installer file action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearInstallerFilesAction()
|
||||
{
|
||||
|
||||
if (!\DUP_CTRL_Tools::isDiagnosticPage() || get_option(self::OPTION_KEY_MIGRATION_SUCCESS_NOTICE) == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (sanitize_text_field(SnapUtil::filterInputRequest('action')) === 'installer') {
|
||||
if (! wp_verify_nonce($_REQUEST['_wpnonce'], 'duplicator_cleanup_page')) {
|
||||
echo '<p>' . __('Security issue', 'duplicator') . '</p>';
|
||||
exit; // Get out of here bad nounce!
|
||||
}
|
||||
|
||||
?>
|
||||
<div id="message" class="notice notice-success">
|
||||
<?php require DUPLICATOR_LITE_PATH . '/views/parts/migration-clean-installation-files.php'; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a display message in the wp-admin if any reserved files are found
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrationSuccessNotice()
|
||||
{
|
||||
if (get_option(self::OPTION_KEY_MIGRATION_SUCCESS_NOTICE) != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (\DUP_CTRL_Tools::isDiagnosticPage()) {
|
||||
require DUPLICATOR_LITE_PATH . '/views/parts/migration-message.php';
|
||||
} else {
|
||||
require DUPLICATOR_LITE_PATH . '/views/parts/migration-almost-complete.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a display message in the wp-admin if any reserved files are found
|
||||
*
|
||||
* @return string Html formatted text notice warnings
|
||||
*/
|
||||
public static function showReservedFilesNotice()
|
||||
{
|
||||
//Show only on Duplicator pages and Dashboard when plugin is active
|
||||
$dup_active = is_plugin_active('duplicator/duplicator.php');
|
||||
$dup_perm = current_user_can('manage_options');
|
||||
if (!$dup_active || !$dup_perm) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
if (!isset($screen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_installer_cleanup_req = ($screen->id == 'duplicator_page_duplicator-tools' && isset($_GET['action']) && $_GET['action'] == 'installer');
|
||||
if (DUP_Server::hasInstallerFiles() && !$is_installer_cleanup_req) {
|
||||
MigrationMng::renameInstallersPhpFiles();
|
||||
|
||||
$on_active_tab = isset($_GET['section']) ? $_GET['section'] : '';
|
||||
echo '<div class="dup-updated notice notice-success dup-global-error-reserved-files" id="message"><p>';
|
||||
|
||||
//Safe Mode Notice
|
||||
$safe_html = '';
|
||||
if (get_option("duplicator_exe_safe_mode", 0) > 0) {
|
||||
$safe_msg1 = __('Safe Mode:', 'duplicator');
|
||||
$safe_msg2 = __('During the install safe mode was enabled deactivating all plugins.<br/> Please be sure to ', 'duplicator');
|
||||
$safe_msg3 = __('re-activate the plugins', 'duplicator');
|
||||
$safe_html = "<div class='notice-safemode'><b>{$safe_msg1}</b><br/>{$safe_msg2} <a href='plugins.php'>{$safe_msg3}</a>!</div><br/>";
|
||||
}
|
||||
|
||||
//On Tools > Cleanup Page
|
||||
if ($screen->id == 'duplicator_page_duplicator-tools' && ($on_active_tab == "info" || $on_active_tab == '')) {
|
||||
$title = __('This site has been successfully migrated!', 'duplicator');
|
||||
$msg1 = __('Final step(s):', 'duplicator');
|
||||
$msg2 = __('This message will be removed after all installer files are removed. Installer files must be removed to maintain a secure site. '
|
||||
. 'Click the link above or button below to remove all installer files and complete the migration.', 'duplicator');
|
||||
|
||||
echo "<b class='pass-msg'><i class='fa fa-check-circle'></i> " . esc_html($title) .
|
||||
"</b> <br/> {$safe_html} <b>" . esc_html($msg1) . "</b> <br/>";
|
||||
printf(
|
||||
"1. <a href='javascript:void(0)' onclick='jQuery(\"#dup-remove-installer-files-btn\").click()'>%s</a><br/>",
|
||||
esc_html__('Remove Installation Files Now!', 'duplicator')
|
||||
);
|
||||
printf(
|
||||
"2. <a href='https://wordpress.org/support/plugin/duplicator/reviews/#new-post' target='wporg'>%s</a> <br/> ",
|
||||
esc_html__('Optionally, Review Duplicator at WordPress.org...', 'duplicator')
|
||||
);
|
||||
echo "<div class='pass-msg'>" . esc_html($msg2) . "</div>";
|
||||
|
||||
//All other Pages
|
||||
} else {
|
||||
$title = __('Migration Almost Complete!', 'duplicator');
|
||||
$msg = __(
|
||||
'Reserved Duplicator installation files have been detected in the root directory. Please delete these installation files to '
|
||||
. 'avoid security issues. <br/> Go to: Duplicator > Tools > General > Information > Utils and click the "Remove Installation Files" button',
|
||||
'duplicator'
|
||||
);
|
||||
|
||||
$nonce = wp_create_nonce('duplicator_cleanup_page');
|
||||
$url = ControllersManager::getMenuLink(
|
||||
ControllersManager::TOOLS_SUBMENU_SLUG,
|
||||
'diagnostics',
|
||||
null,
|
||||
array(
|
||||
'section' => 'info',
|
||||
'_wpnonce' => $nonce,
|
||||
),
|
||||
true
|
||||
);
|
||||
echo "<b>{$title}</b><br/> {$safe_html} {$msg}";
|
||||
@printf("<br/><a href='{$url}'>%s</a>", __('Take me there now!', 'duplicator'));
|
||||
}
|
||||
echo "</p></div>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a message for redirecting a page
|
||||
*
|
||||
* @param string $location The location to redirect to
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
public static function redirect($location)
|
||||
{
|
||||
echo '<div class="dup-redirect"><i class="fas fa-circle-notch fa-spin fa-fw"></i>';
|
||||
esc_html__('Redirecting Please Wait...', 'duplicator');
|
||||
echo '</div>';
|
||||
echo "<script>window.location = '{$location}';</script>";
|
||||
die(esc_html__('Invalid token permissions to perform this request.', 'duplicator'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows install deactivated function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function installAutoDeactivatePlugins()
|
||||
{
|
||||
$reactivatePluginsAfterInstallation = get_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL, false);
|
||||
|
||||
$pluginsToActive = get_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL, false);
|
||||
if (!is_array($pluginsToActive) || empty($pluginsToActive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$shouldBeActivated = array();
|
||||
$allPlugins = get_plugins();
|
||||
foreach ($pluginsToActive as $index => $pluginSlug) {
|
||||
if (!isset($allPlugins[$pluginSlug])) {
|
||||
unset($pluginsToActive[$index]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$isActive = is_plugin_active($pluginSlug);
|
||||
|
||||
if (!$isActive && isset($allPlugins[$pluginSlug])) {
|
||||
$shouldBeActivated[$pluginSlug] = $allPlugins[$pluginSlug]['Name'];
|
||||
} else {
|
||||
unset($pluginsToActive[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($shouldBeActivated)) {
|
||||
delete_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL);
|
||||
return;
|
||||
} else {
|
||||
update_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL, $pluginsToActive);
|
||||
}
|
||||
|
||||
$activatePluginsAnchors = array();
|
||||
foreach ($shouldBeActivated as $slug => $title) {
|
||||
$activateURL = wp_nonce_url(admin_url('plugins.php?action=activate&plugin=' . $slug), 'activate-plugin_' . $slug);
|
||||
$anchorTitle = sprintf(esc_html__('Activate %s', 'duplicator'), $title);
|
||||
$activatePluginsAnchors[] = '<a href="' . $activateURL . '"
|
||||
title="' . esc_attr($anchorTitle) . '">' .
|
||||
$title . '</a>';
|
||||
}
|
||||
?>
|
||||
<div class="update-nag duplicator-plugin-activation-admin-notice notice notice-warning duplicator-admin-notice is-dismissible"
|
||||
data-to-dismiss="<?php echo esc_attr(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL); ?>" >
|
||||
<p>
|
||||
<?php
|
||||
echo "<b>" . esc_html__("Warning!", "duplicator") . "</b> " . esc_html__("Migration Almost Complete!", "duplicator") . " <br/>";
|
||||
echo esc_html__(
|
||||
"Plugin(s) listed here have been deactivated during installation to help prevent issues. Please activate them to finish this migration: ",
|
||||
"duplicator"
|
||||
) . "<br/>";
|
||||
echo implode(' ,', $activatePluginsAnchors);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows install deactivated function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function failedOneClickUpgradeNotice()
|
||||
{
|
||||
if (SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'action') !== 'upgrade_finalize_fail') {
|
||||
return;
|
||||
}
|
||||
|
||||
Notice::error(__('Upgrade failed. Please check if you have the necessary permissions to activate plugins.', 'duplicator'), 'upgrade_finalize_fail');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows feedback notices after certain no. of packages successfully created.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function showFeedBackNotice()
|
||||
{
|
||||
$notice_id = 'rate_us_feedback';
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notices = get_user_meta(get_current_user_id(), DUPLICATOR_ADMIN_NOTICES_USER_META_KEY, true);
|
||||
if (empty($notices)) {
|
||||
$notices = array();
|
||||
}
|
||||
|
||||
$duplicator_pages = array(
|
||||
'toplevel_page_duplicator',
|
||||
'duplicator_page_duplicator-tools',
|
||||
'duplicator_page_duplicator-settings',
|
||||
'duplicator_page_duplicator-gopro',
|
||||
);
|
||||
|
||||
if (!in_array(get_current_screen()->id, $duplicator_pages) || (isset($notices[$notice_id]) && 'true' === $notices[$notice_id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
// not using DUP_Util::getTablePrefix() in place of $tablePrefix because AdminNotices included initially (Duplicator\Lite\Requirement
|
||||
// is depended on the AdminNotices)
|
||||
$tablePrefix = (is_multisite() && is_plugin_active_for_network('duplicator/duplicator.php')) ?
|
||||
$wpdb->base_prefix :
|
||||
$wpdb->prefix;
|
||||
$tableName = esc_sql($tablePrefix . 'duplicator_packages');
|
||||
$packagesCount = $wpdb->get_var("SELECT count(id) FROM `{$tableName}` WHERE status=100");
|
||||
|
||||
if ($packagesCount < DUPLICATOR_FEEDBACK_NOTICE_SHOW_AFTER_NO_PACKAGE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notices[$notice_id] = 'false';
|
||||
update_user_meta(get_current_user_id(), DUPLICATOR_ADMIN_NOTICES_USER_META_KEY, $notices);
|
||||
$dismiss_url = wp_nonce_url(
|
||||
add_query_arg(array(
|
||||
'action' => 'duplicator_set_admin_notice_viewed',
|
||||
'notice_id' => esc_attr($notice_id),
|
||||
), admin_url('admin-post.php')),
|
||||
'duplicator_set_admin_notice_viewed',
|
||||
'nonce'
|
||||
);
|
||||
?>
|
||||
<div class="notice updated duplicator-message duplicator-message-dismissed" data-notice_id="<?php echo esc_attr($notice_id); ?>">
|
||||
<div class="duplicator-message-inner">
|
||||
<div class="duplicator-message-icon">
|
||||
<img
|
||||
src="<?php echo esc_url(DUPLICATOR_PLUGIN_URL . "assets/img/logo.png"); ?>"
|
||||
style="text-align:top; margin:0; height:60px; width:60px;" alt="Duplicator">
|
||||
</div>
|
||||
<div class="duplicator-message-content">
|
||||
<p>
|
||||
<strong>
|
||||
<?php echo __('Congrats!', 'duplicator'); ?>
|
||||
</strong>
|
||||
<?php
|
||||
printf(
|
||||
esc_html__(
|
||||
'You created over %d backups with Duplicator. Great job! If you can spare a minute,
|
||||
please help us by leaving a five star review on WordPress.org.',
|
||||
'duplicator'
|
||||
),
|
||||
DUPLICATOR_FEEDBACK_NOTICE_SHOW_AFTER_NO_PACKAGE
|
||||
); ?>
|
||||
</p>
|
||||
<p class="duplicator-message-actions">
|
||||
<a
|
||||
href="https://wordpress.org/support/plugin/duplicator/reviews/#new-post"
|
||||
target="_blank" class="button button-primary duplicator-notice-rate-now"
|
||||
>
|
||||
<?php esc_html_e("Sure! I'd love to help", 'duplicator'); ?>
|
||||
</a>
|
||||
<a href="<?php echo esc_url($dismiss_url); ?>" class="button duplicator-notice-dismiss">
|
||||
<?php esc_html_e('Hide Notification', 'duplicator'); ?>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a display message in the wp-admin if the logged in user role has not export capability
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function showNoExportCapabilityNotice()
|
||||
{
|
||||
if (is_admin() && in_array('administrator', $GLOBALS['current_user']->roles) && !current_user_can('export')) {
|
||||
$faqUrl = esc_url(LinkManager::getDocUrl(
|
||||
'how-to-resolve-duplicator-plugin-user-interface-ui-issues',
|
||||
'admin_notice',
|
||||
'duplicator menu missing'
|
||||
));
|
||||
$errorMessage = __(
|
||||
'<strong>Duplicator</strong><hr> Your logged-in user role does not have export
|
||||
capability so you don\'t have access to Duplicator functionality.',
|
||||
'duplicator'
|
||||
) .
|
||||
"<br>" .
|
||||
sprintf(
|
||||
_x(
|
||||
'<strong>RECOMMENDATION:</strong> Add export capability to your role. See FAQ: ' .
|
||||
'%1$sWhy is the Duplicator/Packages menu missing from my admin menu?%2$s',
|
||||
'%1$s and %2$s are <a> tags',
|
||||
'duplicator'
|
||||
),
|
||||
'<a target="_blank" href="' . $faqUrl . '">',
|
||||
'</a>'
|
||||
);
|
||||
self::displayGeneralAdminNotice($errorMessage, self::GEN_ERROR_NOTICE, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display multisite notice
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function multisiteNotice()
|
||||
{
|
||||
if (
|
||||
!ControllersManager::isDuplicatorPage() ||
|
||||
ControllersManager::isCurrentPage(ControllersManager::ABOUT_US_SUBMENU_SLUG) ||
|
||||
ControllersManager::isCurrentPage(ControllersManager::SETTINGS_SUBMENU_SLUG, 'general')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = TplMng::getInstance()->render('parts/notices/drm_multisite_msg', array(), false);
|
||||
self::displayGeneralAdminNotice($message, self::GEN_ERROR_NOTICE, false, ['duplicator-multisite-notice']);
|
||||
}
|
||||
|
||||
/**
|
||||
* display genral admin notice by printing it
|
||||
*
|
||||
* @param string $htmlMsg html code to be printed
|
||||
* @param integer $noticeType constant value of SELF::GEN_
|
||||
* @param boolean $isDismissible whether the notice is dismissable or not. Default is true
|
||||
* @param array|string $extraClasses add more classes to the notice div
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function displayGeneralAdminNotice($htmlMsg, $noticeType, $isDismissible = true, $extraClasses = array())
|
||||
{
|
||||
if (empty($extraClasses)) {
|
||||
$classes = array();
|
||||
} elseif (is_array($extraClasses)) {
|
||||
$classes = $extraClasses;
|
||||
} else {
|
||||
$classes = array($extraClasses);
|
||||
}
|
||||
|
||||
$classes[] = 'notice';
|
||||
|
||||
switch ($noticeType) {
|
||||
case self::GEN_INFO_NOTICE:
|
||||
$classes[] = 'notice-info';
|
||||
break;
|
||||
case self::GEN_SUCCESS_NOTICE:
|
||||
$classes[] = 'notice-success';
|
||||
break;
|
||||
case self::GEN_WARNING_NOTICE:
|
||||
$classes[] = 'notice-warning';
|
||||
break;
|
||||
case self::GEN_ERROR_NOTICE:
|
||||
$classes[] = 'notice-error';
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid Admin notice type!');
|
||||
}
|
||||
|
||||
if ($isDismissible) {
|
||||
$classes[] = 'is-dismissible';
|
||||
}
|
||||
|
||||
$classesStr = implode(' ', $classes);
|
||||
?>
|
||||
<div class="<?php echo esc_attr($classesStr); ?>">
|
||||
<p>
|
||||
<?php
|
||||
if (self::GEN_ERROR_NOTICE == $noticeType) {
|
||||
?>
|
||||
<i class='fa fa-exclamation-triangle'></i>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
echo $htmlMsg;
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
291
html/wp-content/plugins/duplicator/src/Views/DashboardWidget.php
Normal file
291
html/wp-content/plugins/duplicator/src/Views/DashboardWidget.php
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Views;
|
||||
|
||||
use DUP_Package;
|
||||
use DUP_PackageStatus;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
|
||||
/**
|
||||
* Dashboard widget
|
||||
*/
|
||||
class DashboardWidget
|
||||
{
|
||||
const LAST_PACKAGE_TIME_WARNING = 86400; // 24 hours
|
||||
const LAST_PACKAGES_LIMIT = 3;
|
||||
const RECOMMENDED_PLUGIN_ENABLED = true;
|
||||
const RECOMMENDED_PLUGIN_DISMISSED_OPT_KEY = 'duplicator_recommended_plugin_dismissed';
|
||||
|
||||
/**
|
||||
* Add the dashboard widget
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
if (is_multisite()) {
|
||||
add_action('wp_network_dashboard_setup', array(__CLASS__, 'addDashboardWidget'));
|
||||
} else {
|
||||
add_action('wp_dashboard_setup', array(__CLASS__, 'addDashboardWidget'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the dashboard widget
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addDashboardWidget()
|
||||
{
|
||||
if (!current_user_can('administrator')) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_add_dashboard_widget(
|
||||
'duplicator_dashboard_widget',
|
||||
__('Duplicator', 'duplicator'),
|
||||
array(__CLASS__, 'renderContent')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the dashboard widget content
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function renderContent()
|
||||
{
|
||||
TplMng::getInstance()->setStripSpaces(true);
|
||||
?>
|
||||
<div class="dup-dashboard-widget-content">
|
||||
<?php self::renderPackageCreate(); ?>
|
||||
<hr class="separator" >
|
||||
<?php self::renderRecentlyPackages(); ?>
|
||||
<hr class="separator" >
|
||||
<?php
|
||||
self::renderSections();
|
||||
if (self::RECOMMENDED_PLUGIN_ENABLED) { // @phpstan-ignore-line
|
||||
self::renderRecommendedPluginSection();
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the package create button
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function renderPackageCreate()
|
||||
{
|
||||
TplMng::getInstance()->render(
|
||||
'parts/DashboardWidget/package-create-section',
|
||||
array (
|
||||
'lastBackupString' => self::getLastBackupString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the last packages
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function renderRecentlyPackages()
|
||||
{
|
||||
/** @var DUP_Package[] */
|
||||
$packages = DUP_Package::get_packages_by_status(
|
||||
array(
|
||||
array(
|
||||
'op' => '>=',
|
||||
'status' => DUP_PackageStatus::COMPLETE
|
||||
)
|
||||
),
|
||||
self::LAST_PACKAGES_LIMIT,
|
||||
0,
|
||||
'created DESC'
|
||||
);
|
||||
|
||||
$totalsIds = DUP_Package::get_ids_by_status(
|
||||
array(
|
||||
array(
|
||||
'op' => '>=',
|
||||
'status' => DUP_PackageStatus::COMPLETE
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$failuresIds = DUP_Package::get_ids_by_status(
|
||||
array(
|
||||
array(
|
||||
'op' => '<',
|
||||
'status' => 0
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
TplMng::getInstance()->render(
|
||||
'parts/DashboardWidget/recently-packages',
|
||||
array(
|
||||
'packages' => $packages,
|
||||
'totalPackages' => count($totalsIds),
|
||||
'totalFailures' => count($failuresIds)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Duplicate sections
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function renderSections()
|
||||
{
|
||||
TplMng::getInstance()->render(
|
||||
'parts/DashboardWidget/sections-section',
|
||||
array(
|
||||
'numSchedules' => 0,
|
||||
'numSchedulesEnabled' => 0,
|
||||
'numTemplates' => 1,
|
||||
'numStorages' => 1,
|
||||
'nextScheduleString' => '',
|
||||
'recoverDateString' => ''
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last backup string
|
||||
*
|
||||
* @return string HTML string
|
||||
*/
|
||||
public static function getLastBackupString()
|
||||
{
|
||||
if (DUP_Package::isPackageRunning()) {
|
||||
return '<span class="spinner"></span> <b>' . esc_html__('A Backup is currently running.', 'duplicator') . '</b>';
|
||||
}
|
||||
|
||||
/** @var DUP_Package[] */
|
||||
$lastPackage = DUP_Package::get_packages_by_status(
|
||||
array(
|
||||
array(
|
||||
'op' => '>=',
|
||||
'status' => DUP_PackageStatus::COMPLETE
|
||||
)
|
||||
),
|
||||
1,
|
||||
0,
|
||||
'created DESC'
|
||||
);
|
||||
|
||||
if (empty($lastPackage)) {
|
||||
return '<b>' . esc_html__('No Backups have been created yet.', 'duplicator') . '</b>';
|
||||
}
|
||||
|
||||
$createdTime = date_i18n(get_option('date_format'), strtotime($lastPackage[0]->Created));
|
||||
|
||||
if ($lastPackage[0]->getPackageLife() > self::LAST_PACKAGE_TIME_WARNING) {
|
||||
$timeDiffClass = 'maroon';
|
||||
} else {
|
||||
$timeDiffClass = 'green';
|
||||
}
|
||||
|
||||
$timeDiff = sprintf(
|
||||
_x('%s ago', '%s represents the time diff, eg. 2 days', 'duplicator'),
|
||||
$lastPackage[0]->getPackageLife('human')
|
||||
);
|
||||
|
||||
return '<b>' . $createdTime . '</b> ' .
|
||||
" (" . '<span class="' . $timeDiffClass . '"><b>' .
|
||||
$timeDiff .
|
||||
'</b></span>' . ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return randomly chosen one of recommended plugins.
|
||||
*
|
||||
* @return false|array{name: string,slug: string,more: string,pro: array{file: string}}
|
||||
*/
|
||||
protected static function getRecommendedPluginData()
|
||||
{
|
||||
$plugins = array(
|
||||
'google-analytics-for-wordpress/googleanalytics.php' => array(
|
||||
'name' => __('MonsterInsights', 'duplicator'),
|
||||
'slug' => 'google-analytics-for-wordpress',
|
||||
'more' => 'https://www.monsterinsights.com/',
|
||||
'pro' => array(
|
||||
'file' => 'google-analytics-premium/googleanalytics-premium.php',
|
||||
),
|
||||
),
|
||||
'all-in-one-seo-pack/all_in_one_seo_pack.php' => array(
|
||||
'name' => __('AIOSEO', 'duplicator'),
|
||||
'slug' => 'all-in-one-seo-pack',
|
||||
'more' => 'https://aioseo.com/',
|
||||
'pro' => array(
|
||||
'file' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php',
|
||||
),
|
||||
),
|
||||
'coming-soon/coming-soon.php' => array(
|
||||
'name' => __('SeedProd', 'duplicator'),
|
||||
'slug' => 'coming-soon',
|
||||
'more' => 'https://www.seedprod.com/',
|
||||
'pro' => array(
|
||||
'file' => 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php',
|
||||
),
|
||||
),
|
||||
'wp-mail-smtp/wp_mail_smtp.php' => array(
|
||||
'name' => __('WP Mail SMTP', 'duplicator'),
|
||||
'slug' => 'wp-mail-smtp',
|
||||
'more' => 'https://wpmailsmtp.com/',
|
||||
'pro' => array(
|
||||
'file' => 'wp-mail-smtp-pro/wp_mail_smtp.php',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$installed = get_plugins();
|
||||
|
||||
foreach ($plugins as $id => $plugin) {
|
||||
if (isset($installed[$id])) {
|
||||
unset($plugins[$id]);
|
||||
}
|
||||
|
||||
if (isset($installed[$plugin['pro']['file']])) {
|
||||
unset($plugins[$id]);
|
||||
}
|
||||
}
|
||||
return ($plugins ? $plugins[ array_rand($plugins) ] : false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recommended plugin block HTML.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function renderRecommendedPluginSection()
|
||||
{
|
||||
if (get_user_meta(get_current_user_id(), self::RECOMMENDED_PLUGIN_DISMISSED_OPT_KEY, true) != false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugin = self::getRecommendedPluginData();
|
||||
|
||||
if (empty($plugin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$installUrl = wp_nonce_url(
|
||||
self_admin_url('update.php?action=install-plugin&plugin=' . rawurlencode($plugin['slug'])),
|
||||
'install-plugin_' . $plugin['slug']
|
||||
);
|
||||
|
||||
TplMng::getInstance()->render(
|
||||
'parts/DashboardWidget/recommended-section',
|
||||
array(
|
||||
'plugin' => $plugin,
|
||||
'installUrl' => $installUrl,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
*/
|
||||
|
||||
namespace Duplicator\Views;
|
||||
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
use Duplicator\Utils\Upsell;
|
||||
|
||||
class EducationElements
|
||||
{
|
||||
const DUP_SETTINGS_FOOTER_CALLOUT_DISMISSED = 'duplicator_settings_footer_callout_dismissed';
|
||||
const DUP_PACKAGES_BOTTOM_BAR_DISMISSED = 'duplicator_packages_bottom_bar_dismissed';
|
||||
const DUP_EMAIL_SUBSCRIBED_OPT_KEY = 'duplicator_email_subscribed';
|
||||
|
||||
/**
|
||||
* Init hooks
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
add_action('duplicator_settings_page_footer', array(__CLASS__, 'displayCalloutCTA'));
|
||||
add_action('duplicator_scan_progress_header', array(__CLASS__, 'didYouKnow'));
|
||||
add_action('duplicator_scan_progress_footer', array(__CLASS__, 'emailForm'));
|
||||
add_action('duplicator_build_progress_header', array(__CLASS__, 'didYouKnow'));
|
||||
add_action('duplicator_build_progress_footer', array(__CLASS__, 'emailForm'));
|
||||
add_action('duplicator_build_success_footer', array(__CLASS__, 'emailForm'));
|
||||
add_action('duplicator_before_packages_footer', array(__CLASS__, 'bottomBar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display callout CTA
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function displayCalloutCTA()
|
||||
{
|
||||
if (get_user_meta(get_current_user_id(), self::DUP_SETTINGS_FOOTER_CALLOUT_DISMISSED, false) == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
TplMng::getInstance()->render('parts/Education/callout-cta');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display did you know
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function didYouKnow()
|
||||
{
|
||||
$features = Upsell::getProFeatureList();
|
||||
TplMng::getInstance()->render('parts/Education/did-you-know-blurb', array(
|
||||
'feature' => $features[array_rand($features)]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display email form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function emailForm()
|
||||
{
|
||||
if (self::userIsSubscribed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TplMng::getInstance()->render('parts/Education/subscribe-form');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display did you know
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bottomBar()
|
||||
{
|
||||
if (get_user_meta(get_current_user_id(), self::DUP_PACKAGES_BOTTOM_BAR_DISMISSED, false) == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$numberOfPackages = \DUP_Package::count_by_status(array(
|
||||
array('op' => '=' , 'status' => \DUP_PackageStatus::COMPLETE )
|
||||
));
|
||||
|
||||
if ($numberOfPackages < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$features = self::getBottomBarFeatures();
|
||||
TplMng::getInstance()->render('parts/Education/packages-bottom-bar', array(
|
||||
'feature' => $features[array_rand($features)]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get packages bottom bar feature list
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getBottomBarFeatures()
|
||||
{
|
||||
return array(
|
||||
__('Scheduled Backups - Ensure that important data is regularly and consistently backed up, allowing for quick ' .
|
||||
'and efficient recovery in case of data loss.', 'duplicator'),
|
||||
__('Cloud Backups - Back up to Dropbox, FTP, Google Drive, OneDrive, or Amazon S3 and more for safe storage.', 'duplicator'),
|
||||
__('Recovery Points - Recovery Points provides protection against mistakes and bad updates by letting you ' .
|
||||
'quickly rollback your system to a known, good state.', 'duplicator'),
|
||||
__('Secure File Encryption - Protect and secure the archive file with industry-standard AES-256 encryption', 'duplicator'),
|
||||
__('Server to Server Import - Direct Backup import from source server or cloud storage using URL. No need ' .
|
||||
'to download the Backup to your desktop machine first.', 'duplicator'),
|
||||
__('File & Database Table Filters - Use file and database filters to pick and choose exactly what you want to ' .
|
||||
'backup or transfer. No bloat!', 'duplicator'),
|
||||
__('Large Site Support - Duplicator Pro has developed a new way to package backups especially tailored for ' .
|
||||
'larger site. No server timeouts or other restrictions.', 'duplicator'),
|
||||
__('Multisite Support - Duplicator Pro supports multisite network backup & migration. You can even install ' .
|
||||
'a subsite as a standalone site.', 'duplicator'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if user is subscribed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function userIsSubscribed()
|
||||
{
|
||||
return get_user_meta(get_current_user_id(), self::DUP_EMAIL_SUBSCRIBED_OPT_KEY, false);
|
||||
}
|
||||
}
|
||||
26
html/wp-content/plugins/duplicator/src/Views/ViewHelper.php
Normal file
26
html/wp-content/plugins/duplicator/src/Views/ViewHelper.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Duplicator
|
||||
*/
|
||||
|
||||
namespace Duplicator\Views;
|
||||
|
||||
use Duplicator\Core\Controllers\ControllersManager;
|
||||
use Duplicator\Core\Views\TplMng;
|
||||
|
||||
class ViewHelper
|
||||
{
|
||||
/**
|
||||
* Display Duplicator Logo on all pages
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function adminLogoHeader()
|
||||
{
|
||||
if (!ControllersManager::isDuplicatorPage()) {
|
||||
return;
|
||||
}
|
||||
TplMng::getInstance()->render('parts/admin-logo-header');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user