2605 lines
65 KiB
PHP
2605 lines
65 KiB
PHP
<?php
|
|
/**
|
|
* WP Multisite WaaS 1.X to 2.X migrator.
|
|
*
|
|
* @package WP_Ultimo
|
|
* @subpackage Installers
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
namespace WP_Ultimo\Installers;
|
|
|
|
use Psr\Log\LogLevel;
|
|
use Ifsnop\Mysqldump\Mysqldump;
|
|
use WP_Ultimo\UI\Template_Previewer;
|
|
use WP_Ultimo\Models\Checkout_Form;
|
|
use WP_Ultimo\Checkout\Legacy_Checkout;
|
|
use WP_Ultimo\Database\Memberships\Membership_Status;
|
|
use WP_Ultimo\Database\Payments\Payment_Status;
|
|
use WP_Ultimo\Database\Sites\Site_Type;
|
|
use WP_Ultimo\Managers\Domain_Manager;
|
|
use WP_Ultimo\Managers\Limitation_Manager;
|
|
|
|
// Exit if accessed directly
|
|
defined('ABSPATH') || exit;
|
|
|
|
/**
|
|
* WP Multisite WaaS 1.X to 2.X migrator.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
class Migrator extends Base_Installer {
|
|
|
|
use \WP_Ultimo\Traits\Singleton;
|
|
|
|
const LOG_FILE_NAME = 'migrator-errors';
|
|
|
|
/**
|
|
* Holds the session object.
|
|
*
|
|
* @since 2.0.0
|
|
* @var \WP_Ultimo\Session
|
|
*/
|
|
public $session;
|
|
|
|
/**
|
|
* Errors holder.
|
|
*
|
|
* @since 2.0.0
|
|
* @var array
|
|
*/
|
|
public $errors;
|
|
|
|
/**
|
|
* Back traces holder.
|
|
*
|
|
* @since 2.0.7
|
|
* @var array
|
|
*/
|
|
public $back_traces;
|
|
|
|
/**
|
|
* Legacy settings cache.
|
|
*
|
|
* @since 2.0.0
|
|
* @var array
|
|
*/
|
|
protected $settings;
|
|
|
|
/**
|
|
* Dry run status.
|
|
*
|
|
* @since 2.0.7
|
|
* @var boolean
|
|
*/
|
|
protected $dry_run = true;
|
|
|
|
/**
|
|
* If we should run tests on a limited set or not.
|
|
*
|
|
* @since 2.0.7
|
|
* @var boolean
|
|
*/
|
|
protected $run_tests_on_limited_set = false;
|
|
|
|
/**
|
|
* List of ids of interest.
|
|
*
|
|
* @since 2.0.7
|
|
* @var array
|
|
*/
|
|
protected $ids_of_interest = [];
|
|
|
|
/**
|
|
* The status of our attempts to bypass server limitations.
|
|
*
|
|
* This will return false if the bypass was never tried, true if it was successful,
|
|
* or a WP_Error object containing the errors.
|
|
*
|
|
* @since 2.0.7
|
|
* @var bool|\WP_Error
|
|
*/
|
|
protected $server_bypass_status = true;
|
|
|
|
/**
|
|
* Initializes the session object to keep track of errors.
|
|
*
|
|
* @since 2.0.0
|
|
* @return void
|
|
*/
|
|
public function init(): void {
|
|
|
|
if ($this->is_migration_screen()) {
|
|
$this->session = wu_get_session('migrator');
|
|
|
|
$this->errors = $this->session->get('errors');
|
|
|
|
$this->back_traces = $this->session->get('back_traces');
|
|
}
|
|
|
|
/*
|
|
* Install the handler for the parallel installer.
|
|
*/
|
|
\WP_Ultimo\Async_Calls::register_listener('parallel_installers', [$this, 'handle_parallel_installers']);
|
|
|
|
\WP_Ultimo\Async_Calls::install_listeners();
|
|
}
|
|
|
|
/**
|
|
* Handle the parallel installers via the Async Caller.
|
|
*
|
|
* @since 2.0.7
|
|
* @return array
|
|
*/
|
|
public function handle_parallel_installers() {
|
|
|
|
ob_start();
|
|
|
|
do_action('wp_ajax_wu_setup_install'); // phpcs:ignore
|
|
|
|
$results = json_decode(ob_get_clean(), true);
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Handles unexpected shutdowns of the PHP process.
|
|
*
|
|
* @since 2.0.7
|
|
*
|
|
* @param \WP_Ultimo\Session $session The session handler object.
|
|
* @param bool $dry_run If we are in dry run mode or not.
|
|
* @param string $installer The name of the current installer.
|
|
* @return void
|
|
*/
|
|
public function on_shutdown($session, $dry_run, $installer): void {
|
|
|
|
$message = 'The migrator process exit unexpectedly while running the "%s" migration... You might need to increase server resources to run the migrator. Below, a list of the ids of interest collected thus far:';
|
|
|
|
wu_log_add(self::LOG_FILE_NAME, sprintf($message, $installer), LogLevel::ERROR);
|
|
|
|
$this->log_ids_of_interest();
|
|
|
|
$e = new \Exception('The migrator process exit unexpectedly');
|
|
|
|
$this->handle_error_messages($e, $session, $dry_run, $installer);
|
|
}
|
|
|
|
/**
|
|
* Checks if we are on the migration screen to prevent issues.
|
|
*
|
|
* @since 2.0.0
|
|
* @return boolean
|
|
*/
|
|
protected function is_migration_screen() {
|
|
|
|
return wu_request('page') === 'wp-ultimo-setup' && wu_request('step') === 'migration';
|
|
}
|
|
|
|
/**
|
|
* Checks if the migration is done.
|
|
*
|
|
* @since 2.0.7
|
|
* @return boolean
|
|
*/
|
|
public static function is_migration_done() {
|
|
|
|
$plans = get_posts(
|
|
[
|
|
'post_type' => 'wpultimo_plan',
|
|
'numberposts' => 1,
|
|
]
|
|
);
|
|
|
|
if (empty($plans)) {
|
|
/*
|
|
* For all intents and purposes, a fresh install
|
|
* can be considered as "migrated", as there is nothing
|
|
* to migrate.
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
return get_network_option(null, 'wu_is_migration_done', false);
|
|
}
|
|
|
|
/**
|
|
* Check if we are running on a network that runs Ultimo 1.X
|
|
*
|
|
* @since 2.0.0
|
|
* @return boolean
|
|
*/
|
|
public static function is_legacy_network() {
|
|
/*
|
|
* Here we need to check if we already run the migration.
|
|
* If we already runned or if this is a fresh install, is_migration_done
|
|
* will return true, indicating that this is not a legacy network at this moment.
|
|
*/
|
|
return ! self::is_migration_done();
|
|
}
|
|
|
|
/**
|
|
* Returns the list of errors detected.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
public function get_errors(): array {
|
|
|
|
return array_unique((array) $this->errors);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of _backtraces detected.
|
|
*
|
|
* @since 2.0.7
|
|
*/
|
|
public function get_back_traces(): array {
|
|
|
|
return array_unique((array) $this->back_traces);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of migration steps.
|
|
*
|
|
* @since 2.0.0
|
|
* @param bool $force_all If you want to get all the steps despite of dry run status.
|
|
* @return array
|
|
*/
|
|
public function get_steps($force_all = false) {
|
|
|
|
$steps = [];
|
|
|
|
$dry_run = wu_request('dry-run', true);
|
|
|
|
if ($dry_run && ! $force_all) {
|
|
$steps['dry_run_check'] = [
|
|
'title' => __('Pre-Migration Check', 'wp-ultimo'),
|
|
'description' => __('Runs all migrations in a sand-boxed environment to see if it hits an error.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'pending' => __('Pending', 'wp-ultimo'),
|
|
'installing' => __('Checking...', 'wp-ultimo'),
|
|
'success' => __('Success!', 'wp-ultimo'),
|
|
'done' => false,
|
|
];
|
|
|
|
return $steps;
|
|
}
|
|
|
|
if ( ! $dry_run) {
|
|
$steps['backup'] = [
|
|
'title' => __('Prepare for Migration', 'wp-ultimo'),
|
|
'description' => __('Verifies the data before going forward with the migration.', 'wp-ultimo'),
|
|
'pending' => __('Pending', 'wp-ultimo'),
|
|
'installing' => __('Preparing...', 'wp-ultimo'),
|
|
'success' => __('Success!', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
}
|
|
|
|
$steps['settings'] = [
|
|
'title' => __('Settings', 'wp-ultimo'),
|
|
'description' => __('Migrates the settings from the older version.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['products'] = [
|
|
'title' => __('Plans to Products', 'wp-ultimo'),
|
|
'description' => __('Converts the old plans into products.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['customers'] = [
|
|
'title' => __('Users to Customers', 'wp-ultimo'),
|
|
'description' => __('Creates customers based on the existing users.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['memberships'] = [
|
|
'title' => __('Subscriptions to Memberships', 'wp-ultimo'),
|
|
'description' => __('Converts subscriptions into Memberships.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['transactions'] = [
|
|
'title' => __('Transactions to Payments & Events', 'wp-ultimo'),
|
|
'description' => __('Converts transactions into payments and events.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['discount_codes'] = [
|
|
'title' => __('Coupons to Discount Codes', 'wp-ultimo'),
|
|
'description' => __('Converts coupons into discount codes.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['sites'] = [
|
|
'title' => __('Customer Sites', 'wp-ultimo'),
|
|
'description' => __('Adjusts existing customer sites.', 'wp-ultimo'),
|
|
'installing' => __('Making Adjustments...', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['site_templates'] = [
|
|
'title' => __('Sites Templates', 'wp-ultimo'),
|
|
'description' => __('Adjusts existing site templates.', 'wp-ultimo'),
|
|
'installing' => __('Making Adjustments...', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['domains'] = [
|
|
'title' => __('Mapped Domains', 'wp-ultimo'),
|
|
'description' => __('Converts mapped domains.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['forms'] = [
|
|
'title' => __('Checkout Forms', 'wp-ultimo'),
|
|
'description' => __('Creates a checkout form based on the existing signup flow.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['emails'] = [
|
|
'title' => __('Emails & Broadcasts', 'wp-ultimo'),
|
|
'description' => __('Converts the emails and broadcasts.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['webhooks'] = [
|
|
'title' => __('Webhooks', 'wp-ultimo'),
|
|
'description' => __('Migrates existing webhooks.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps['other'] = [
|
|
'title' => __('Other Migrations', 'wp-ultimo'),
|
|
'description' => __('Other migrations that don\'t really fit anywhere else.', 'wp-ultimo'),
|
|
'help' => wu_get_documentation_url('migration-errors'),
|
|
'done' => false,
|
|
];
|
|
|
|
$steps = array_map(
|
|
fn($item) => wp_parse_args(
|
|
$item,
|
|
[
|
|
'pending' => __('Pending', 'wp-ultimo'),
|
|
'installing' => __('Migrating...', 'wp-ultimo'),
|
|
'success' => __('Success!', 'wp-ultimo'),
|
|
]
|
|
),
|
|
$steps
|
|
);
|
|
|
|
/**
|
|
* Allow developers and add-ons to add new migration steps
|
|
*
|
|
* @since 2.0.0
|
|
* @param array $steps The list of steps.
|
|
* @param \WP_Ultimo\Migrator $this This class.
|
|
*/
|
|
$steps = apply_filters('wu_get_migration_steps', $steps, $this);
|
|
|
|
return $steps;
|
|
}
|
|
|
|
/**
|
|
* Tries to bypass server limitations such as memory and time limits.
|
|
*
|
|
* @since 2.0.7
|
|
* @return void
|
|
*/
|
|
protected function bypass_server_limits() {
|
|
|
|
$error = new \WP_Error();
|
|
|
|
$message = 'Unable to set %s';
|
|
|
|
/*
|
|
* Attempt to set the memory limit.
|
|
*/
|
|
$set_memory_limit = @ini_set('memory_limit', '-1'); // phpcs:ignore
|
|
|
|
if (false === $set_memory_limit) {
|
|
$error->add('memory_limit', sprintf($message, 'memory_limit'));
|
|
}
|
|
|
|
/*
|
|
* Attempt to set the time limit.
|
|
*/
|
|
$set_time_limit = @set_time_limit(0); // phpcs:ignore
|
|
|
|
if (false === $set_time_limit) {
|
|
$error->add('time_limit', sprintf($message, 'time_limit'));
|
|
}
|
|
|
|
/*
|
|
* It's interesting if we can prevent errors from showing up and breaking
|
|
* the ajax response, but if we can't set it, it doesn't really matter in terms
|
|
* of being able to execute the migrations or not.
|
|
*
|
|
* This can be gracefully handled on the front-end if necessary.
|
|
*/
|
|
@ini_set('display_errors', 0); // phpcs:ignore
|
|
|
|
if ($error->has_errors()) {
|
|
$this->server_bypass_status = $error;
|
|
|
|
return;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Handles the installer.
|
|
*
|
|
* This wraps the installer into a try catch block
|
|
* so we can use that to rollback on database entries.
|
|
*
|
|
* Migrator needs a different implementation to support
|
|
* dry runs.
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @param bool|\WP_Error $status Status of the installer.
|
|
* @param string $installer The installer name.
|
|
* @param object $wizard Wizard class.
|
|
* @return bool
|
|
*/
|
|
public function handle($status, $installer, $wizard) {
|
|
|
|
global $wpdb, $wu_migrator_current_installer;
|
|
|
|
/*
|
|
* Set the dry run status, which is true by default.
|
|
*/
|
|
$this->dry_run = wu_request('dry-run', true);
|
|
|
|
$callable = [$this, "_install_{$installer}"];
|
|
|
|
$callable = apply_filters("wu_installer_{$installer}_callback", $callable, $installer);
|
|
|
|
/*
|
|
* No installer on this class.
|
|
*/
|
|
if ( ! is_callable($callable)) {
|
|
return $status;
|
|
}
|
|
|
|
/*
|
|
* Try to bypass the server limits such as timeouts, etc.
|
|
*/
|
|
$this->bypass_server_limits();
|
|
|
|
$opening_log_message = sprintf('Migration starting for %s with dry mode %s...', $installer, $this->dry_run ? 'on' : 'off');
|
|
|
|
if ($this->is_parallel()) {
|
|
$opening_log_message .= sprintf(' This is a parallel request with a page attribute of %d and a per_page attribute of %d', wu_request('page', 0), wu_request('per_page', 0));
|
|
}
|
|
|
|
wu_log_add(self::LOG_FILE_NAME, $opening_log_message);
|
|
|
|
/*
|
|
* Check if we had any errors on setting the server limits.
|
|
* If that's the case, we only run the dry run in a limited number of items.
|
|
*
|
|
* That's no ideal in detecting possible errors, but let's face it,
|
|
* if some migration can break, it will probably break within the first 10-20
|
|
* records anyway...
|
|
*/
|
|
if (is_wp_error($this->server_bypass_status)) {
|
|
/*
|
|
* Set the flag that tells the migrator to only run on
|
|
* a limited set of records for the check.
|
|
*/
|
|
$this->run_tests_on_limited_set = true;
|
|
|
|
$log_message = implode(PHP_EOL, $this->server_bypass_status->get_error_messages());
|
|
|
|
wu_log_add(self::LOG_FILE_NAME, $log_message, LogLevel::ERROR);
|
|
}
|
|
|
|
$session = wu_get_session('migrator');
|
|
|
|
/*
|
|
* Registers a shutdown handler.
|
|
*/
|
|
// register_shutdown_function(array($this, 'on_shutdown'), $session, $this->dry_run, $installer);
|
|
|
|
try {
|
|
wp_cache_flush();
|
|
|
|
$wpdb->query('START TRANSACTION');
|
|
|
|
call_user_func($callable, wu_request('per_page'), wu_request('page'));
|
|
} catch (\Throwable $e) {
|
|
$wpdb->query('ROLLBACK');
|
|
|
|
/*
|
|
* Log errors to later reference.
|
|
*/
|
|
wu_log_add(self::LOG_FILE_NAME, $e->__toString(), LogLevel::ERROR);
|
|
|
|
return $this->handle_error_messages($e, $session, $this->dry_run, $installer);
|
|
}
|
|
|
|
/*
|
|
* Log ids of interest.
|
|
*/
|
|
$this->log_ids_of_interest();
|
|
|
|
/*
|
|
* Commit or rollback depending on the status
|
|
*/
|
|
if ($this->dry_run) {
|
|
$wpdb->query('ROLLBACK');
|
|
} else {
|
|
$wpdb->query('COMMIT');
|
|
|
|
wp_cache_flush();
|
|
}
|
|
|
|
if ($session) {
|
|
$session->set('errors', []);
|
|
|
|
$session->set('back_traces', []);
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* Handles error messages and exceptions so we can display them and log them.
|
|
*
|
|
* @since 2.0.7
|
|
*
|
|
* @param \Throwable|null $e The exception thrown.
|
|
* @param \WP_Ultimo\Session $session THe WP Multisite WaaS session object.
|
|
* @param boolean $dry_run If we are on a dry run or not.
|
|
* @param string $installer The name of the installer.
|
|
* @return \WP_Error
|
|
*/
|
|
public function handle_error_messages($e, $session, $dry_run = true, $installer = 'none') {
|
|
|
|
global $wu_migrator_current_installer;
|
|
|
|
$caller = $dry_run ? $wu_migrator_current_installer : $installer;
|
|
|
|
// Translators: %s is the name of the installer.
|
|
$error_nice_message = sprintf(__('Critical error found when migrating "%s".', 'wp-ultimo'), $caller);
|
|
|
|
if ($session) {
|
|
$errors = (array) $session->get('errors');
|
|
|
|
$back_traces = (array) $session->get('back_traces');
|
|
|
|
$errors[] = $error_nice_message;
|
|
|
|
$back_traces[] = $e->__toString();
|
|
|
|
$session->set('errors', $errors);
|
|
|
|
$session->set('back_traces', $back_traces);
|
|
}
|
|
|
|
return new \WP_Error($installer, $error_nice_message);
|
|
}
|
|
|
|
/**
|
|
* Dumps ids of interest on the log so we can revise them later id needed.
|
|
*
|
|
* @since 2.0.7
|
|
* @return void
|
|
*/
|
|
public function log_ids_of_interest(): void {
|
|
|
|
if ( ! is_array($this->ids_of_interest)) {
|
|
wu_log_add(self::LOG_FILE_NAME, 'The list of IDs of interested got corrupted, this might indicate problems with one or more migrations', LogLevel::ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
foreach ($this->ids_of_interest as $key => $ids) {
|
|
[$installer, $reason] = explode(':', $key);
|
|
|
|
if (empty($ids)) {
|
|
continue;
|
|
}
|
|
|
|
$id_list = implode(PHP_EOL, $ids);
|
|
|
|
$message = sprintf('The following IDs where skipped from the "%s" migration due to an status of "%s": %s', $installer, $reason, PHP_EOL . $id_list);
|
|
|
|
wu_log_add(self::LOG_FILE_NAME, $message, LogLevel::WARNING);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an id of interest to the list.
|
|
*
|
|
* @since 2.0.7
|
|
*
|
|
* @param string|array $id_or_ids One or more ids to be added.
|
|
* @param string $reason The reason why this is an id of interest.
|
|
* @param string $installer The installer name.
|
|
* @return void
|
|
*/
|
|
public function add_id_of_interest($id_or_ids, $reason, $installer): void {
|
|
|
|
$id_list_key = sprintf('%s:%s', $installer, $reason);
|
|
|
|
$id_list = wu_get_isset($this->ids_of_interest, $id_list_key, []);
|
|
|
|
$id_or_ids = (array) $id_or_ids;
|
|
|
|
$this->ids_of_interest[ $id_list_key ] = array_merge($id_list, $id_or_ids);
|
|
}
|
|
|
|
/**
|
|
* Generate the database dump as a backup.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return mixed
|
|
*/
|
|
public function _install_dry_run_check(): void {
|
|
|
|
global $wpdb, $wu_migrator_current_installer;
|
|
|
|
$all_steps = $this->get_steps(true);
|
|
|
|
foreach ($all_steps as $installer => $step) {
|
|
$wu_migrator_current_installer = $installer;
|
|
|
|
$callable = [$this, "_install_{$installer}"];
|
|
|
|
call_user_func($callable);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate the database dump as a backup.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return mixed
|
|
*/
|
|
public function _install_backup(): void {
|
|
|
|
global $wpdb;
|
|
|
|
$folder = wu_maybe_create_folder('wu-backup');
|
|
|
|
$file_name = $folder . date_i18n('Y-m-d-his') . '-wu-dump.sql';
|
|
|
|
$dump = new MySQLDump(
|
|
sprintf('mysql:dbname=%s;host=%s', DB_NAME, DB_HOST),
|
|
DB_USER,
|
|
DB_PASSWORD,
|
|
[
|
|
'compress' => MySQLDump::GZIP,
|
|
]
|
|
);
|
|
|
|
$dump->start($file_name);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of legacy settings on 1.X.
|
|
*
|
|
* @since 2.0.0
|
|
* @return array
|
|
*/
|
|
public function get_old_settings() {
|
|
|
|
global $wpdb;
|
|
|
|
if ($this->settings !== null) {
|
|
return $this->settings;
|
|
}
|
|
|
|
$settings = $wpdb->get_var(
|
|
"
|
|
SELECT meta_value
|
|
FROM
|
|
{$wpdb->base_prefix}sitemeta
|
|
WHERE
|
|
meta_key = 'wp-ultimo_settings'
|
|
LIMIT 1
|
|
"
|
|
);
|
|
|
|
$this->settings = maybe_unserialize($settings);
|
|
|
|
return $this->settings;
|
|
}
|
|
|
|
/**
|
|
* Returns the value of a particular legacy setting.
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @param string $setting The setting key.
|
|
* @param boolean $default Default value.
|
|
* @return mixed
|
|
*/
|
|
public function get_old_setting($setting, $default = false) {
|
|
|
|
$settings = $this->get_old_settings();
|
|
|
|
$value = wu_get_isset($settings, $setting, $default);
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Migrates the settings.
|
|
*
|
|
* @todo Needs implementing.
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_settings() {
|
|
|
|
$settings = $this->get_old_settings();
|
|
|
|
$random_key = time();
|
|
|
|
/*
|
|
* Save current options as backup.
|
|
*/
|
|
wu_save_option("v1_settings_{$random_key}", $settings);
|
|
|
|
$keys_to_migrate = [
|
|
// General
|
|
'currency_symbol',
|
|
'currency_position',
|
|
'decimal_separator',
|
|
'thousand_separator',
|
|
'precision',
|
|
|
|
// Products & Legacy Pricing Table
|
|
'default_pricing_option',
|
|
'enable_price_1',
|
|
'enable_price_3',
|
|
'enable_price_12',
|
|
'enable_multiple_domains',
|
|
'domain_options',
|
|
|
|
// Dev Options
|
|
'enable_error_reporting',
|
|
'uninstall_wipe_tables',
|
|
|
|
// Registration
|
|
'block_frontend',
|
|
'obfuscate_original_login_url',
|
|
'block_frontend_grace_period',
|
|
'enable_multiple_sites',
|
|
'default_role',
|
|
'add_users_to_main_site',
|
|
'main_site_default_role',
|
|
|
|
// Memberships
|
|
'move_posts_on_downgrade',
|
|
'block_sites_on_downgrade',
|
|
|
|
// Gateways
|
|
'active_gateways',
|
|
'attach_invoice_pdf',
|
|
|
|
// PayPal
|
|
'paypal_username',
|
|
'paypal_pass',
|
|
'paypal_signature',
|
|
'paypal_sandbox',
|
|
'paypal_standard',
|
|
'paypal_standard_email',
|
|
|
|
// Jumper
|
|
'jumper_key',
|
|
'jumper_custom_links',
|
|
|
|
// Domain Mapping
|
|
'enable_domain_mapping',
|
|
'custom_domains',
|
|
'force_admin_redirect',
|
|
|
|
// Site Templates
|
|
'allow_template_switching',
|
|
'allow_own_site_as_template',
|
|
'copy_media',
|
|
|
|
// WordPress menus
|
|
'menu_items_plugin',
|
|
'add_new_users',
|
|
|
|
// Other options we want to keep.
|
|
'limits_and_quotas',
|
|
];
|
|
|
|
$to_migrate = array_intersect_key($settings, array_flip($keys_to_migrate));
|
|
|
|
/** Additional settings to migrate */
|
|
$to_migrate['primary_color'] = $this->get_old_setting('primary-color', '#00a1ff');
|
|
$to_migrate['accent_color'] = $this->get_old_setting('accent-color', '#78b336');
|
|
|
|
/*
|
|
* Enable Registration
|
|
*/
|
|
$to_migrate['enable_registration'] = $this->get_old_setting('enable_signup', true);
|
|
|
|
/*
|
|
* Activate Multiple Accounts
|
|
*
|
|
* @since 2.0.7
|
|
*/
|
|
$to_migrate['enable_multiple_accounts'] = class_exists('\WU_Multiple_Logins');
|
|
|
|
/*
|
|
* Company Address
|
|
*/
|
|
$to_migrate['company_address'] = $this->get_old_setting('merchant_address', true);
|
|
|
|
/*
|
|
* Gateway
|
|
*/
|
|
$active_gateways = array_keys($this->get_old_setting('active_gateway', []));
|
|
$to_migrate['active_gateways'] = $active_gateways;
|
|
|
|
/*
|
|
* Stripe
|
|
*/
|
|
$is_sandbox = str_contains((string) $this->get_old_setting('stripe_api_sk', ''), 'test');
|
|
|
|
$to_migrate['stripe_sandbox_mode'] = $is_sandbox;
|
|
|
|
$to_migrate['stripe_should_collect_billing_address'] = $this->get_old_setting('stripe_should_collect_billing_address', false);
|
|
|
|
$to_migrate['stripe_test_pk_key'] = $this->get_old_setting('stripe_api_pk', '');
|
|
|
|
$to_migrate['stripe_test_sk_key'] = $this->get_old_setting('stripe_api_sk', '');
|
|
|
|
$to_migrate['stripe_live_pk_key'] = $this->get_old_setting('stripe_api_pk', '');
|
|
|
|
$to_migrate['stripe_live_sk_key'] = $this->get_old_setting('stripe_api_sk', '');
|
|
|
|
/*
|
|
* PayPal
|
|
*/
|
|
$is_paypal_sandbox = $this->get_old_setting('paypal_sandbox_mode', true);
|
|
$to_migrate['paypal_sandbox_mode'] = $is_paypal_sandbox;
|
|
|
|
if ($is_paypal_sandbox) {
|
|
$to_migrate['paypal_test_username'] = $this->get_old_setting('paypal_username', '');
|
|
$to_migrate['paypal_test_password'] = $this->get_old_setting('paypal_pass', '');
|
|
$to_migrate['paypal_test_signature'] = $this->get_old_setting('paypal_signature', '');
|
|
} else {
|
|
$to_migrate['paypal_live_username'] = $this->get_old_setting('paypal_username', '');
|
|
$to_migrate['paypal_live_password'] = $this->get_old_setting('paypal_pass', '');
|
|
$to_migrate['paypal_live_signature'] = $this->get_old_setting('paypal_signature', '');
|
|
}
|
|
|
|
/*
|
|
* API Settings
|
|
*/
|
|
$api_key = wp_generate_password(24);
|
|
$api_secret = wp_generate_password(24);
|
|
|
|
$to_migrate['enable_api'] = $this->get_old_setting('enable-api', true);
|
|
$to_migrate['api_key'] = $this->get_old_setting('api-key', $api_key);
|
|
$to_migrate['api_secret'] = $this->get_old_setting('api-secret', $api_secret);
|
|
$to_migrate['api_log_calls'] = $this->get_old_setting('api-log-calls', false);
|
|
$to_migrate['webhook_calls_blocking'] = $this->get_old_setting('webhook-calls-blocking', false);
|
|
|
|
/*
|
|
* Top-bar Settings
|
|
*/
|
|
$top_bar_settings = [
|
|
'enabled' => $this->get_old_setting('allow_template_top_bar', true),
|
|
'preview_url_parameter' => 'template-preview',
|
|
'bg_color' => $this->get_old_setting('top-bar-bg-color', '#f9f9f9'),
|
|
'button_bg_color' => $this->get_old_setting('top-bar-button-bg-color', '#00a1ff'),
|
|
'button_text' => $this->get_old_setting('top-bar-button-text', __('Use this Template', 'wp-ultimo')),
|
|
'display_responsive_controls' => $this->get_old_setting('top-bar-enable-resize', true),
|
|
'use_custom_logo' => $this->get_old_setting('top-bar-use-logo'),
|
|
'custom_logo' => $this->get_old_setting('top-bar-logo'),
|
|
];
|
|
|
|
$save_settings = Template_Previewer::get_instance()->save_settings($top_bar_settings);
|
|
|
|
/*
|
|
* Fake registers missing settings so we can save them.
|
|
*/
|
|
$this->fake_register_settings($to_migrate);
|
|
|
|
/*
|
|
* Save Migrated Settings.
|
|
*/
|
|
$status = \WP_Ultimo\Settings::get_instance()->save_settings($to_migrate);
|
|
}
|
|
|
|
/**
|
|
* Register missing setting keys so they can be saved.
|
|
*
|
|
* @since 2.0.7
|
|
*
|
|
* @param array $to_migrate The list of settings to migrate.
|
|
* @return array
|
|
*/
|
|
public function fake_register_settings($to_migrate = []) {
|
|
|
|
add_filter(
|
|
'wu_settings_section_core_fields',
|
|
function ($fields) use ($to_migrate) {
|
|
|
|
$all_keys = \WP_Ultimo\Settings::get_instance()->get_all();
|
|
|
|
$missing_keys = array_diff_key($to_migrate, $all_keys);
|
|
|
|
foreach ($missing_keys as $field_slug => $value) {
|
|
$fields[ $field_slug ] = [
|
|
'type' => 'hidden',
|
|
'setting_id' => $field_slug,
|
|
'raw' => true,
|
|
];
|
|
}
|
|
|
|
return $fields;
|
|
},
|
|
10
|
|
);
|
|
|
|
\WP_Ultimo\Settings::get_instance()->default_sections();
|
|
|
|
$sections = \WP_Ultimo\Settings::get_instance()->get_sections();
|
|
|
|
return $sections;
|
|
}
|
|
|
|
/**
|
|
* Migrates Plans.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_products() {
|
|
|
|
global $wpdb;
|
|
|
|
/*
|
|
* Load dependencies.
|
|
*/
|
|
require_once wu_path('inc/functions/product.php');
|
|
|
|
$settings = wu_get_option('settings');
|
|
|
|
$product_type_plan = 'plan';
|
|
$duration_unit_day = 'month';
|
|
$recurring = true;
|
|
$currency = $settings['currency'] ?? 'USD';
|
|
|
|
$plans = $wpdb->get_results(
|
|
"
|
|
SELECT
|
|
ID,
|
|
post_title name,
|
|
post_name slug,
|
|
post_date,
|
|
post_modified
|
|
FROM
|
|
{$wpdb->base_prefix}posts
|
|
WHERE
|
|
post_type = 'wpultimo_plan'
|
|
ORDER BY ID ASC
|
|
"
|
|
);
|
|
|
|
$default_billing = $this->get_old_setting('default_pricing_option', 1);
|
|
|
|
foreach ($plans as $plan) {
|
|
/*
|
|
* Skip errors if a plan exists.
|
|
*/
|
|
if (wu_get_product_by_slug($plan->slug)) {
|
|
continue;
|
|
}
|
|
|
|
$product_data = [];
|
|
$product_data['type'] = 'plan';
|
|
$product_data['migrated_from_id'] = $plan->ID;
|
|
$product_data['name'] = $plan->name;
|
|
$product_data['slug'] = $plan->slug;
|
|
$product_data['recurring'] = $recurring;
|
|
$product_data['currency'] = $currency;
|
|
|
|
/*
|
|
* Set the dates.
|
|
*/
|
|
$product_data['date_created'] = $plan->post_date;
|
|
$product_data['date_modified'] = $plan->post_modified;
|
|
|
|
$product_data['description'] = get_post_meta($plan->ID, 'wpu_description', true);
|
|
$product_data['setup_fee'] = get_post_meta($plan->ID, 'wpu_setup_fee', true);
|
|
$product_data['featured_plan'] = (bool) get_post_meta($plan->ID, 'wpu_top_deal', true);
|
|
$product_data['feature_list'] = get_post_meta($plan->ID, 'wpu_feature_list', true);
|
|
$product_data['customer_role'] = get_post_meta($plan->ID, 'wpu_role', true);
|
|
$product_data['list_order'] = get_post_meta($plan->ID, 'wpu_order', true);
|
|
|
|
/*
|
|
* Trial
|
|
*/
|
|
$default_trial_value = $this->get_old_setting('trial', 0);
|
|
$plan_trial_value = get_post_meta($plan->ID, 'wpu_trial', true);
|
|
$product_data['trial_duration'] = $plan_trial_value ?: $default_trial_value;
|
|
$product_data['trial_duration_unit'] = $duration_unit_day;
|
|
|
|
$active = ! (bool) get_post_meta($plan->ID, 'wpu_hidden', true);
|
|
$product_data['active'] = $active;
|
|
|
|
$is_free = get_post_meta($plan->ID, 'wpu_free', true);
|
|
|
|
$price_variations = [];
|
|
|
|
if ($is_free) {
|
|
$product_data['amount'] = 0;
|
|
$product_data['pricing_type'] = 'free';
|
|
} else {
|
|
$price_month = get_post_meta($plan->ID, 'wpu_price_1', true);
|
|
|
|
if ($price_month) {
|
|
if (absint($default_billing) === 1) {
|
|
$product_data['amount'] = $price_month;
|
|
$product_data['duration'] = 1;
|
|
$product_data['duration_unit'] = 'month';
|
|
} else {
|
|
$price_variations[] = [
|
|
'amount' => $price_month,
|
|
'duration' => 1,
|
|
'duration_unit' => 'month',
|
|
];
|
|
}
|
|
}
|
|
|
|
$price_3_month = get_post_meta($plan->ID, 'wpu_price_3', true);
|
|
|
|
if ($price_3_month) {
|
|
if (absint($default_billing) === 3) {
|
|
$product_data['amount'] = $price_3_month;
|
|
$product_data['duration'] = 3;
|
|
$product_data['duration_unit'] = 'month';
|
|
} else {
|
|
$price_variations[] = [
|
|
'amount' => $price_3_month,
|
|
'duration' => 3,
|
|
'duration_unit' => 'month',
|
|
];
|
|
}
|
|
}
|
|
|
|
$price_12_month = get_post_meta($plan->ID, 'wpu_price_12', true);
|
|
|
|
if ($price_12_month) {
|
|
if (absint($default_billing) === 12) {
|
|
$product_data['amount'] = $price_12_month;
|
|
$product_data['duration'] = 1;
|
|
$product_data['duration_unit'] = 'year';
|
|
} else {
|
|
$price_variations[] = [
|
|
'amount' => $price_12_month,
|
|
'duration' => 1,
|
|
'duration_unit' => 'year',
|
|
];
|
|
}
|
|
}
|
|
|
|
$is_contact_us = (bool) get_post_meta($plan->ID, 'wpu_is_contact_us', true);
|
|
$product_data['pricing_type'] = $is_contact_us ? 'contact_us' : 'paid';
|
|
}
|
|
|
|
/*
|
|
* Set the pricing variations.
|
|
*/
|
|
$product_data['price_variations'] = $price_variations;
|
|
|
|
/*
|
|
* Gets the rest of the meta data.
|
|
*/
|
|
$all_product_meta = get_post_meta($plan->ID);
|
|
|
|
/*
|
|
* Fixes multiple values
|
|
*/
|
|
$all_product_meta = array_map('array_pop', $all_product_meta);
|
|
|
|
/*
|
|
* Attaches meta to product creation
|
|
*/
|
|
$product_data['meta'] = $all_product_meta;
|
|
|
|
/*
|
|
* Creates product
|
|
*/
|
|
$product = wu_create_product($product_data);
|
|
|
|
if (is_wp_error($product)) {
|
|
throw new \Exception($product->get_error_message());
|
|
}
|
|
|
|
/*
|
|
* Migrate Quota
|
|
*/
|
|
$quotas = (array) get_post_meta($plan->ID, 'wpu_quotas', true);
|
|
$disabled_post_types = array_keys((array) get_post_meta($plan->ID, 'wpu_disabled_post_types', true));
|
|
$allowed_plugins = (array) get_post_meta($plan->ID, 'wpu_allowed_plugins', true);
|
|
$allowed_themes = (array) get_post_meta($plan->ID, 'wpu_allowed_themes', true);
|
|
$has_custom_domain = (bool) get_post_meta($plan->ID, 'wpu_custom_domain', true);
|
|
|
|
$site_template_mode = 'default';
|
|
$has_template_selection_options = (bool) $this->get_old_setting('allow_template', true);
|
|
$templates = array_filter(array_keys((array) get_post_meta($plan->ID, 'wpu_templates', true)));
|
|
$has_custom_template_list = (bool) get_post_meta($plan->ID, 'wpu_override_templates', true);
|
|
$site_template_enabled = true;
|
|
$force_template = false;
|
|
|
|
if (false === $has_template_selection_options) {
|
|
$force_template = get_post_meta($plan->ID, 'wpu_site_template', true);
|
|
|
|
if ($force_template && wu_get_site($force_template)) {
|
|
$site_template_mode = 'assign_template';
|
|
}
|
|
|
|
$has_custom_template_list = false;
|
|
} elseif ($has_custom_template_list) {
|
|
$site_template_mode = 'choose_available_templates';
|
|
|
|
if (empty($templates)) {
|
|
$site_template_enabled = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Build template list.
|
|
*/
|
|
$site_templates_limit = [];
|
|
|
|
$available_site_templates = wu_get_site_templates();
|
|
|
|
foreach ($available_site_templates as $site_template) {
|
|
$site_template_id = absint($site_template->get_id());
|
|
|
|
$behavior = $has_custom_template_list ? 'not_available' : 'available';
|
|
|
|
if (absint($force_template) === $site_template_id) {
|
|
$behavior = 'pre_selected';
|
|
} elseif (in_array($site_template_id, $templates)) { // phpcs:ignore;
|
|
|
|
$behavior = 'available';
|
|
}
|
|
|
|
$site_templates_limit[ $site_template_id ] = [
|
|
'behavior' => $behavior,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Limitation modules.
|
|
*/
|
|
$limitations_modules = [
|
|
'domain_mapping' => [
|
|
'enabled' => $has_custom_domain,
|
|
],
|
|
'customer_user_role' => [
|
|
'enabled' => true,
|
|
'limit' => $product_data['customer_role'],
|
|
],
|
|
'site_templates' => [
|
|
'mode' => $site_template_mode,
|
|
'enabled' => $site_template_enabled,
|
|
'limit' => $site_templates_limit,
|
|
],
|
|
];
|
|
|
|
/**
|
|
* Build the plugins limitations object.
|
|
*
|
|
* @since 2.0.7
|
|
*/
|
|
if (is_array($allowed_plugins)) {
|
|
$allowed_plugins = $allowed_plugins;
|
|
|
|
$plugins_limit = [];
|
|
|
|
foreach (Limitation_Manager::get_instance()->get_all_plugins() as $plugin_path => &$plugin_data) {
|
|
$plugins_limit[ $plugin_path ] = [
|
|
'visibility' => in_array($plugin_path, $allowed_plugins, true) ? 'visible' : 'hidden',
|
|
'behavior' => in_array($plugin_path, $allowed_plugins, true) ? 'available' : 'not_available',
|
|
];
|
|
}
|
|
|
|
$limitations_modules['plugins'] = [
|
|
'limit' => $plugins_limit,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Build the themes limitations object.
|
|
*
|
|
* @since 2.0.7
|
|
*/
|
|
if (is_array($allowed_themes)) {
|
|
$themes_limit = [];
|
|
|
|
foreach (Limitation_Manager::get_instance()->get_all_themes() as $stylesheet => &$theme_data) {
|
|
$themes_limit[ $stylesheet ] = [
|
|
'visibility' => in_array($stylesheet, $allowed_themes, true) ? 'visible' : 'hidden',
|
|
'behavior' => in_array($stylesheet, $allowed_themes, true) ? 'available' : 'not_available',
|
|
];
|
|
}
|
|
|
|
$limitations_modules['themes'] = [
|
|
'limit' => $themes_limit,
|
|
];
|
|
}
|
|
|
|
$unlimited_users = (bool) get_post_meta($plan->ID, 'wpu_unlimited_extra_users', true);
|
|
|
|
$post_types_limit = [];
|
|
|
|
foreach ($quotas as $post_type => $quota) {
|
|
if ('users' === $post_type && ! $unlimited_users) {
|
|
$user_roles = get_editable_roles();
|
|
|
|
$roles_limit = [];
|
|
|
|
foreach ($user_roles as $role => $role_data) {
|
|
$roles_limit[ $role ] = [
|
|
'enabled' => true,
|
|
'number' => $quota ? absint($quota) : '',
|
|
];
|
|
}
|
|
|
|
$limitations_modules['users'] = [
|
|
'limit' => $roles_limit,
|
|
'enabled' => true,
|
|
];
|
|
} elseif ('upload' === $post_type) {
|
|
$limitations_modules['disk_space'] = [
|
|
'limit' => absint($quota),
|
|
'enabled' => true,
|
|
];
|
|
} elseif ('visits' === $post_type) {
|
|
$limitations_modules['visits'] = [
|
|
'limit' => $quota ? absint($quota) : '',
|
|
'enabled' => true,
|
|
];
|
|
} elseif ('sites' === $post_type) {
|
|
$limitations_modules['sites'] = [
|
|
'limit' => absint($quota),
|
|
'enabled' => true,
|
|
];
|
|
} else {
|
|
$post_types_limit[ $post_type ] = [
|
|
'enabled' => in_array($post_type, $disabled_post_types, true) === false,
|
|
'number' => $quota ? absint($quota) : '',
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If there's any limitations to post types, add it.
|
|
*/
|
|
if ($post_types_limit) {
|
|
$limitations_modules['post_types'] = [
|
|
'limit' => $post_types_limit,
|
|
'enabled' => true,
|
|
];
|
|
}
|
|
|
|
$limitations = new \WP_Ultimo\Objects\Limitations($limitations_modules);
|
|
|
|
$product->update_meta('wu_limitations', $limitations);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies if we are inside a parallel request or not.
|
|
*
|
|
* @since 2.0.7
|
|
* @return boolean
|
|
*/
|
|
protected function is_parallel() {
|
|
|
|
return wu_request('parallel') && wu_request('page') && wu_request('per_page');
|
|
}
|
|
|
|
/**
|
|
* Get the installer name based on the request.
|
|
*
|
|
* This method is useful especially inside the dry_run test,
|
|
* where all installers are run, but the only installer actually
|
|
* being called is the dry_run check. With this method,
|
|
* we can have access to the correct installer even on that
|
|
* scenario.
|
|
*
|
|
* @since 2.0.7
|
|
* @return string
|
|
*/
|
|
protected function get_installer() {
|
|
|
|
global $wu_migrator_current_installer;
|
|
|
|
return $this->dry_run ? $wu_migrator_current_installer : wu_request('installer', '');
|
|
}
|
|
|
|
/**
|
|
* Decides if we should run this in parallel, based on the request.
|
|
*
|
|
* @since 2.0.7
|
|
*
|
|
* @param int $total_records The total number of records.
|
|
* @param int $threshold The threshold separating normal processing and parallel processing.
|
|
* @return void
|
|
*/
|
|
protected function maybe_run_in_parallel($total_records, $threshold = 100) {
|
|
|
|
if ($this->dry_run) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If this request is not a parallel one,
|
|
* we need to decide if we want to break it up
|
|
* based on the total number of resources.
|
|
*/
|
|
if ($this->is_parallel() === false) {
|
|
if ($total_records > $threshold) {
|
|
$args = [
|
|
'installer' => $this->get_installer(),
|
|
'dry-run' => $this->dry_run,
|
|
];
|
|
|
|
$result = \WP_Ultimo\Async_Calls::run('parallel_installers', $args, $total_records, $threshold, 10);
|
|
|
|
if (is_wp_error($result)) {
|
|
throw new \Exception($result->get_error_message());
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds an SQL limit clause to be used inside the installers.
|
|
*
|
|
* @since 2.0.7
|
|
* @return string
|
|
*/
|
|
protected function build_limit_clause() {
|
|
/*
|
|
* Create an empty limit clause.
|
|
* This can be changed if we are running in parallel.
|
|
*/
|
|
$limit_clause = '';
|
|
|
|
if ($this->dry_run) {
|
|
return sprintf('LIMIT %d', 10);
|
|
} elseif ($this->is_parallel()) {
|
|
$page = absint(wu_request('page'));
|
|
$per_page = absint(wu_request('per_page'));
|
|
$offset = ($page - 1) * $per_page;
|
|
|
|
$limit_clause = sprintf('LIMIT %d,%d', $offset, $per_page);
|
|
}
|
|
|
|
return $limit_clause;
|
|
}
|
|
|
|
/**
|
|
* Migrates Customers.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_customers() {
|
|
|
|
global $wpdb;
|
|
|
|
/*
|
|
* We need the total number of records
|
|
* to decided if we must run this on the
|
|
* background or not.
|
|
*/
|
|
$total_records = (int) $wpdb->get_var("SELECT count(ID) FROM {$wpdb->base_prefix}wu_subscriptions");
|
|
|
|
/*
|
|
* Pass the value to the decider,
|
|
* if we need to run in parallel,
|
|
* the method handled it gracefully
|
|
*/
|
|
$this->maybe_run_in_parallel($total_records);
|
|
|
|
/*
|
|
* Calculates the limit clause.
|
|
*/
|
|
$limit_clause = $this->build_limit_clause();
|
|
|
|
/*
|
|
* Load dependencies.
|
|
*/
|
|
require_once wu_path('inc/functions/customer.php');
|
|
|
|
// phpcs:disable
|
|
$users = $wpdb->get_results(
|
|
"
|
|
SELECT
|
|
user_id,
|
|
created_at
|
|
FROM
|
|
{$wpdb->base_prefix}wu_subscriptions
|
|
{$limit_clause}
|
|
"
|
|
);
|
|
// phpcs:enable
|
|
|
|
foreach ($users as $user) {
|
|
if (wu_get_customer_by_user_id($user->user_id)) {
|
|
continue;
|
|
}
|
|
|
|
$customer = wu_create_customer(
|
|
[
|
|
'user_id' => $user->user_id,
|
|
'email_verification' => 'verified',
|
|
'vip' => false,
|
|
'date_registered' => $user->created_at,
|
|
'last_login' => $user->created_at,
|
|
]
|
|
);
|
|
|
|
if (is_wp_error($customer)) {
|
|
if ($customer->get_error_code() !== 'empty_username') {
|
|
throw new \Exception($customer->get_error_message());
|
|
} else {
|
|
$this->add_id_of_interest($user->user_id, 'not_found', 'customers');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrates Memberships.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_memberships() {
|
|
|
|
global $wpdb;
|
|
|
|
/*
|
|
* We need the total number of records
|
|
* to decided if we must run this on the
|
|
* background or not.
|
|
*/
|
|
$total_records = (int) $wpdb->get_var("SELECT count(ID) FROM {$wpdb->base_prefix}wu_subscriptions");
|
|
|
|
/*
|
|
* Pass the value to the decider,
|
|
* if we need to run in parallel,
|
|
* the method handled it gracefully
|
|
*/
|
|
$this->maybe_run_in_parallel($total_records);
|
|
|
|
/*
|
|
* Calculates the limit clause.
|
|
*/
|
|
$limit_clause = $this->build_limit_clause();
|
|
|
|
/*
|
|
* Load dependencies.
|
|
*/
|
|
require_once wu_path('inc/functions/date.php');
|
|
require_once wu_path('inc/functions/customer.php');
|
|
require_once wu_path('inc/functions/membership.php');
|
|
|
|
$today = gmdate('Y-m-d H:i:s');
|
|
|
|
// phpcs:disable
|
|
$subscriptions = $wpdb->get_results(
|
|
"
|
|
SELECT
|
|
ID,
|
|
user_id,
|
|
plan_id,
|
|
price,
|
|
trial,
|
|
freq,
|
|
active_until,
|
|
created_at,
|
|
gateway,
|
|
integration_key,
|
|
integration_status,
|
|
last_plan_change,
|
|
meta_object as meta
|
|
FROM
|
|
{$wpdb->base_prefix}wu_subscriptions
|
|
{$limit_clause}
|
|
"
|
|
);
|
|
// phpcs:enable
|
|
|
|
foreach ($subscriptions as $subscription) {
|
|
/*
|
|
* If we have already migrated, no need to do it again.
|
|
*/
|
|
if (wu_get_membership_by('migrated_from_id', $subscription->ID)) {
|
|
continue;
|
|
}
|
|
|
|
$customer = wu_get_customer_by_user_id($subscription->user_id);
|
|
|
|
$product = wu_get_product_by('migrated_from_id', absint($subscription->plan_id));
|
|
|
|
$v1_subscription_meta = (object) ($subscription->meta ? maybe_unserialize($subscription->meta) : []);
|
|
|
|
$membership_data = [];
|
|
|
|
if ( ! $product) {
|
|
$this->add_id_of_interest($subscription->plan_id, 'plan_not_migrated', 'memberships');
|
|
|
|
$membership_data['skip_validation'] = true;
|
|
}
|
|
|
|
$membership_data['migrated_from_id'] = $subscription->ID;
|
|
|
|
$membership_data['customer_id'] = $customer ? $customer->get_id() : 0;
|
|
$membership_data['plan_id'] = $product ? $product->get_id() : 0;
|
|
$membership_data['amount'] = $subscription->price;
|
|
$membership_data['disabled'] = false;
|
|
$membership_data['recurring'] = true;
|
|
$membership_data['signup_method'] = 'migrated';
|
|
|
|
$membership_data['status'] = $subscription->active_until < $today ? Membership_Status::EXPIRED : Membership_Status::ACTIVE;
|
|
$membership_data['date_expiration'] = $subscription->active_until;
|
|
$membership_data['date_created'] = $subscription->created_at;
|
|
$membership_data['date_modified'] = $subscription->created_at;
|
|
|
|
switch ( $subscription->freq ) {
|
|
case '1':
|
|
$membership_data['duration_unit'] = 'month';
|
|
break;
|
|
case '3':
|
|
$membership_data['duration_unit'] = 'month';
|
|
break;
|
|
case '12':
|
|
$membership_data['duration_unit'] = 'year';
|
|
break;
|
|
}
|
|
|
|
switch ( $subscription->freq ) {
|
|
case '1':
|
|
$membership_data['duration'] = 1;
|
|
break;
|
|
case '3':
|
|
$membership_data['duration'] = 3;
|
|
break;
|
|
case '12':
|
|
$membership_data['duration'] = 1;
|
|
break;
|
|
}
|
|
|
|
/**
|
|
* Try to fetch the last payment to use it as the last renewal date.
|
|
*
|
|
* @since 2.0.7
|
|
*/
|
|
$last_renewal_date_query = $wpdb->prepare(
|
|
"
|
|
SELECT
|
|
time
|
|
FROM
|
|
{$wpdb->base_prefix}wu_transactions
|
|
WHERE
|
|
user_id = %d AND type = 'payment'
|
|
ORDER BY id DESC
|
|
LIMIT 1
|
|
",
|
|
$subscription->user_id
|
|
);
|
|
|
|
$last_renewal_date = $wpdb->get_var($last_renewal_date_query); // phpcs:ignore
|
|
|
|
if (wu_validate_date($last_renewal_date)) {
|
|
$membership_data['date_renewed'] = $last_renewal_date;
|
|
}
|
|
|
|
/**
|
|
* Try to fetch the initial amount paid.
|
|
*
|
|
* @since 2.0.7
|
|
*/
|
|
$initial_amount_query = $wpdb->prepare(
|
|
"
|
|
SELECT
|
|
amount,
|
|
original_amount
|
|
FROM
|
|
{$wpdb->base_prefix}wu_transactions
|
|
WHERE
|
|
user_id = %d AND type = 'payment'
|
|
ORDER BY id ASC
|
|
LIMIT 1
|
|
",
|
|
$subscription->user_id
|
|
);
|
|
|
|
$initial_amount = $wpdb->get_var($initial_amount_query); // phpcs:ignore
|
|
|
|
if (is_numeric($initial_amount)) {
|
|
$membership_data['initial_amount'] = (float) $initial_amount;
|
|
}
|
|
|
|
/*
|
|
* Handle the default gateway integrations: Stripe and Paypal.
|
|
*/
|
|
if ($subscription->gateway) {
|
|
$membership_data['gateway'] = $subscription->gateway;
|
|
|
|
if ($subscription->gateway === 'stripe') {
|
|
|
|
/**
|
|
* Case Stripe.
|
|
*
|
|
* On Stripe, the integration key on v1 was the customer_id on
|
|
* Stripe. The subscription id was saved on the meta object.
|
|
*/
|
|
$membership_data['gateway_customer_id'] = $subscription->integration_key;
|
|
$membership_data['gateway_subscription_id'] = $v1_subscription_meta->subscription_id;
|
|
} elseif ($subscription->gateway === 'paypal') {
|
|
|
|
/**
|
|
* Case PayPal.
|
|
*
|
|
* For PayPal, the integration key saved is the PROFILE ID.
|
|
* This is what we use as the subscription id on the new models.
|
|
*/
|
|
$membership_data['gateway_subscription_id'] = $subscription->integration_key;
|
|
}
|
|
|
|
if ($subscription->integration_status) {
|
|
$membership_data['auto_renew'] = true;
|
|
}
|
|
}
|
|
|
|
if ($subscription->trial > 0) {
|
|
|
|
/**
|
|
* Here we need to figure out when the trial ended, if it has ended.
|
|
*
|
|
* If not, we just set the new trial date to the future and
|
|
* change the membership status to trialing.
|
|
*
|
|
* @since 2.0.7
|
|
*/
|
|
$subscription_creation_date = wu_date($subscription->created_at);
|
|
|
|
$trial_end_date = $subscription_creation_date->add(new \DateInterval('P' . $subscription->trial . 'D'))->setTime(23, 59, 59);
|
|
|
|
$date_trial_end = $trial_end_date->format('Y-m-d 23:59:59');
|
|
|
|
$membership_data['date_trial_end'] = $date_trial_end;
|
|
|
|
/*
|
|
* Handle memberships still in trial.
|
|
*/
|
|
if (wu_date() < $trial_end_date) {
|
|
$membership_data['status'] = Membership_Status::TRIALING;
|
|
}
|
|
}
|
|
|
|
if (empty(wu_to_float($subscription->price)) || ! $subscription->active_until) {
|
|
$membership_data['status'] = Membership_Status::ACTIVE;
|
|
}
|
|
|
|
/**
|
|
* Count the payments made to set the billing count thus far.
|
|
*
|
|
* @since 2.0.7
|
|
*/
|
|
$payments_count_query = $wpdb->prepare(
|
|
"
|
|
SELECT
|
|
count(id)
|
|
FROM
|
|
{$wpdb->base_prefix}wu_transactions
|
|
WHERE
|
|
user_id = %d AND type = 'payment'
|
|
",
|
|
$subscription->user_id
|
|
);
|
|
|
|
$payments_count = $wpdb->get_var($payments_count_query); // phpcs:ignore
|
|
|
|
$membership_data['times_billed'] = absint($payments_count);
|
|
|
|
$membership_data = array_merge(
|
|
$product ? $product->to_array() : [],
|
|
$customer ? $customer->to_array() : [],
|
|
$membership_data
|
|
);
|
|
|
|
if ( ! $membership_data['customer_id']) {
|
|
$this->add_id_of_interest($subscription->user_id, 'customer_not_migrated', 'memberships');
|
|
|
|
$membership_data['skip_validation'] = true;
|
|
}
|
|
|
|
$membership = wu_create_membership($membership_data);
|
|
|
|
if (is_wp_error($membership)) {
|
|
throw new \Exception($membership->get_error_message());
|
|
}
|
|
|
|
/*
|
|
* Update statuses and check for other info.
|
|
*/
|
|
if ($membership) {
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrates Transactions.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_transactions() {
|
|
|
|
global $wpdb;
|
|
|
|
/*
|
|
* We need the total number of records
|
|
* to decided if we must run this on the
|
|
* background or not.
|
|
*/
|
|
$total_records = (int) $wpdb->get_var("SELECT count(ID) FROM {$wpdb->base_prefix}wu_transactions");
|
|
|
|
/*
|
|
* Pass the value to the decider,
|
|
* if we need to run in parallel,
|
|
* the method handled it gracefully
|
|
*/
|
|
$this->maybe_run_in_parallel($total_records);
|
|
|
|
/*
|
|
* Calculates the limit clause.
|
|
*/
|
|
$limit_clause = $this->build_limit_clause();
|
|
|
|
/*
|
|
* Load dependencies.
|
|
*/
|
|
require_once wu_path('inc/functions/customer.php');
|
|
require_once wu_path('inc/functions/membership.php');
|
|
require_once wu_path('inc/functions/tax.php');
|
|
require_once wu_path('inc/functions/payment.php');
|
|
|
|
// phpcs:disable
|
|
$transactions = $wpdb->get_results(
|
|
"
|
|
SELECT
|
|
*
|
|
FROM
|
|
{$wpdb->base_prefix}wu_transactions
|
|
{$limit_clause}
|
|
"
|
|
);
|
|
// phpcs:enable
|
|
|
|
/**
|
|
* Types to skip when migrating.
|
|
*
|
|
* In the previous version, things that were not payments were also
|
|
* saved as transactions, such as a recurring_setup event.
|
|
* We need to clean those up, skipping them.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
$types_to_skip = [
|
|
'recurring_setup',
|
|
'cancel',
|
|
];
|
|
|
|
$map_status = [
|
|
'payment' => Payment_Status::COMPLETED,
|
|
'failed' => Payment_Status::FAILED,
|
|
'refund' => Payment_Status::REFUND,
|
|
'pending' => Payment_Status::PENDING,
|
|
];
|
|
|
|
foreach ($transactions as $transaction) {
|
|
/*
|
|
* If we have already migrated, no need to do it again.
|
|
*/
|
|
if (wu_get_payment_by('migrated_from_id', $transaction->id)) {
|
|
continue;
|
|
}
|
|
|
|
if (in_array($transaction->type, $types_to_skip, true)) {
|
|
continue;
|
|
}
|
|
|
|
$membership = wu_get_membership_by('user_id', $transaction->user_id);
|
|
|
|
$customer = wu_get_customer_by_user_id($transaction->user_id);
|
|
|
|
$product = '';
|
|
|
|
if (isset($transaction->plan_id)) {
|
|
$product = wu_get_product_by('migrated_from_id', $transaction->plan_id);
|
|
}
|
|
|
|
$line_item = new \WP_Ultimo\Checkout\Line_Item(
|
|
[
|
|
'product' => $product,
|
|
'quantity' => 1,
|
|
]
|
|
);
|
|
|
|
$line_item->set_title($transaction->description);
|
|
$line_item->set_description($transaction->description);
|
|
|
|
$line_item->set_unit_price(wu_to_float($transaction->amount));
|
|
$line_item->set_subtotal(wu_to_float($transaction->amount));
|
|
$line_item->set_total(wu_to_float($transaction->amount));
|
|
|
|
$line_items = [
|
|
$line_item->get_id() => $line_item,
|
|
];
|
|
|
|
$payment_data = [
|
|
'parent' => 0,
|
|
'line_items' => $line_items,
|
|
'status' => wu_get_isset($map_status, $transaction->type, Payment_Status::COMPLETED),
|
|
'customer_id' => $membership ? $customer->get_id() : false,
|
|
'membership_id' => $membership ? $membership->get_id() : false,
|
|
'product_id' => $membership ? $membership->get_plan_id() : false,
|
|
'currency' => $membership ? $membership->get_currency() : false,
|
|
'discount_code' => '',
|
|
'subtotal' => wu_to_float($transaction->amount),
|
|
'discount_total' => 0,
|
|
'tax_total' => 0,
|
|
'total' => wu_to_float($transaction->amount),
|
|
'gateway' => $transaction->gateway,
|
|
'gateway_payment_id' => $transaction->reference_id,
|
|
'migrated_from_id' => $transaction->id,
|
|
'date_created' => $transaction->time,
|
|
'date_modified' => $transaction->time,
|
|
];
|
|
|
|
if ( ! $customer) {
|
|
$this->add_id_of_interest($transaction->user_id, 'customer_not_migrated', 'transactions');
|
|
|
|
$payment_data['skip_validation'] = true;
|
|
}
|
|
|
|
if ( ! $membership) {
|
|
$this->add_id_of_interest($transaction->user_id, 'membership_not_migrated', 'transactions');
|
|
|
|
$payment_data['skip_validation'] = true;
|
|
}
|
|
|
|
$payment = wu_create_payment($payment_data);
|
|
|
|
if (is_wp_error($payment)) {
|
|
throw new \Exception($payment->get_error_message());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrates Coupons.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_discount_codes() {
|
|
|
|
global $wpdb;
|
|
|
|
/*
|
|
* Load dependencies.
|
|
*/
|
|
require_once wu_path('inc/functions/discount-code.php');
|
|
|
|
$coupons = $wpdb->get_results(
|
|
"
|
|
SELECT
|
|
ID,
|
|
post_title name,
|
|
post_name slug,
|
|
post_date date_create,
|
|
post_modified
|
|
FROM
|
|
{$wpdb->base_prefix}posts
|
|
WHERE
|
|
post_type = 'wpultimo_coupon'
|
|
"
|
|
);
|
|
|
|
foreach ($coupons as $coupon) {
|
|
/*
|
|
* If we have already migrated, no need to do it again.
|
|
*/
|
|
if (wu_get_discount_code_by_code($coupon->slug)) {
|
|
continue;
|
|
}
|
|
|
|
$wpu_expiring_date = get_post_meta($coupon->ID, 'wpu_expiring_date', true);
|
|
|
|
if ( ! wu_validate_date($wpu_expiring_date)) {
|
|
$wpu_expiring_date = false;
|
|
}
|
|
|
|
$allowed_plans = get_post_meta($coupon->ID, 'wpu_allowed_plans', true);
|
|
|
|
$limit_products = false;
|
|
|
|
$allowed_products = [];
|
|
|
|
if ($allowed_plans) {
|
|
$limit_products = true;
|
|
|
|
foreach ($allowed_plans as $key => $plan_id) {
|
|
$product = wu_get_product_by('migrated_from_id', $plan_id);
|
|
|
|
if ($product) {
|
|
$allowed_products[] = $product->get_id();
|
|
}
|
|
}
|
|
}
|
|
|
|
$discount_code_data = [
|
|
'uses' => get_post_meta($coupon->ID, 'wpu_uses', true),
|
|
'max_uses' => get_post_meta($coupon->ID, 'wpu_allowed_uses', true),
|
|
'name' => $coupon->name,
|
|
'description' => get_post_meta($coupon->ID, 'wpu_description', true),
|
|
'code' => $coupon->slug,
|
|
'type' => get_post_meta($coupon->ID, 'wpu_type', true),
|
|
'value' => get_post_meta($coupon->ID, 'wpu_value', true),
|
|
'setup_fee_type' => get_post_meta($coupon->ID, 'wpu_setup_fee_discount_type', true),
|
|
'setup_fee_value' => get_post_meta($coupon->ID, 'wpu_setup_fee_discount_value', true),
|
|
'limit_products' => $limit_products,
|
|
'allowed_products' => $allowed_products,
|
|
'date_start' => $coupon->date_create,
|
|
'date_expiration' => $wpu_expiring_date,
|
|
'date_created' => $coupon->date_create,
|
|
'date_modified' => $coupon->post_modified,
|
|
'skip_validation' => true,
|
|
];
|
|
|
|
/*
|
|
* Fix the type name.
|
|
*/
|
|
if (wu_get_isset($discount_code_data, 'type') === 'percent') {
|
|
$discount_code_data['type'] = 'percentage';
|
|
}
|
|
|
|
/*
|
|
* Fix the type name.
|
|
*/
|
|
if (wu_get_isset($discount_code_data, 'setup_fee_type') === 'percent') {
|
|
$discount_code_data['setup_fee_type'] = 'percentage';
|
|
}
|
|
|
|
$discount_code = wu_create_discount_code($discount_code_data);
|
|
|
|
if (is_wp_error($discount_code)) {
|
|
throw new \Exception($discount_code->get_error_message());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrates Sites.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_sites() {
|
|
|
|
global $wpdb;
|
|
|
|
/*
|
|
* We need the total number of records
|
|
* to decided if we must run this on the
|
|
* background or not.
|
|
*/
|
|
$total_records = (int) $wpdb->get_var("SELECT count(ID) FROM {$wpdb->base_prefix}wu_site_owner");
|
|
|
|
/*
|
|
* Pass the value to the decider,
|
|
* if we need to run in parallel,
|
|
* the method handled it gracefully
|
|
*/
|
|
$this->maybe_run_in_parallel($total_records);
|
|
|
|
/*
|
|
* Calculates the limit clause.
|
|
*/
|
|
$limit_clause = $this->build_limit_clause();
|
|
|
|
/*
|
|
* Load dependencies.
|
|
*/
|
|
require_once wu_path('inc/functions/customer.php');
|
|
require_once wu_path('inc/functions/membership.php');
|
|
require_once wu_path('inc/functions/site.php');
|
|
|
|
// phpcs:disable
|
|
$site_owners = $wpdb->get_results(
|
|
"
|
|
SELECT
|
|
site_id,
|
|
user_id
|
|
FROM
|
|
{$wpdb->base_prefix}wu_site_owner
|
|
{$limit_clause}
|
|
"
|
|
);
|
|
// phpcs:enable
|
|
|
|
foreach ($site_owners as $site_owner) {
|
|
$site = wu_get_site($site_owner->site_id);
|
|
$customer = wu_get_customer_by_user_id($site_owner->user_id);
|
|
|
|
$membership = wu_get_membership_by('user_id', $site_owner->user_id);
|
|
|
|
if ( ! $site) {
|
|
continue;
|
|
}
|
|
|
|
if ($customer) {
|
|
$site->set_customer_id($customer->get_id());
|
|
}
|
|
|
|
if ($membership) {
|
|
$site->set_membership_id($membership->get_id());
|
|
}
|
|
|
|
$site->set_type('customer_owned');
|
|
|
|
$saved = $site->save();
|
|
|
|
if (is_wp_error($saved)) {
|
|
throw new \Exception($saved->get_error_message());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrates Template Sites Sites.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_site_templates() {
|
|
/*
|
|
* Load dependencies.
|
|
*/
|
|
require_once wu_path('inc/functions/customer.php');
|
|
require_once wu_path('inc/functions/membership.php');
|
|
require_once wu_path('inc/functions/site.php');
|
|
|
|
$skip_ids = [];
|
|
|
|
/*
|
|
* First thing, add the main site to the skip list.
|
|
*/
|
|
$skip_ids[] = wu_get_main_site_id();
|
|
|
|
$search_arguments = [
|
|
'network_id' => get_current_site()->id,
|
|
'fields' => 'ids',
|
|
'site__not_in' => $skip_ids,
|
|
'no_found_rows' => true,
|
|
];
|
|
|
|
if ($this->dry_run) {
|
|
$per_page = 10;
|
|
$page = 1;
|
|
|
|
$search_arguments['number'] = $per_page;
|
|
$search_arguments['offset'] = ($page - 1) * $per_page;
|
|
} elseif ($this->is_parallel()) {
|
|
$per_page = absint(wu_request('per_page', 10));
|
|
$page = absint(wu_request('page', 1));
|
|
|
|
$search_arguments['number'] = $per_page;
|
|
$search_arguments['offset'] = ($page - 1) * $per_page;
|
|
}
|
|
|
|
$saved_templates = get_sites($search_arguments);
|
|
|
|
if (is_array($saved_templates)) {
|
|
foreach ($saved_templates as $template_id) {
|
|
$site_template = wu_get_site($template_id);
|
|
|
|
if ( ! $site_template) {
|
|
continue;
|
|
}
|
|
|
|
if ($site_template->get_type() === Site_Type::CUSTOMER_OWNED) {
|
|
continue;
|
|
}
|
|
|
|
$site_template->set_type('site_template');
|
|
|
|
/*
|
|
* Get Categories
|
|
*/
|
|
$categories_string = get_blog_option($site_template->get_id(), 'wu_categories', false);
|
|
|
|
$site_template->set_categories(explode(',', (string) $categories_string));
|
|
|
|
/*
|
|
* Saved thumbnails
|
|
*/
|
|
$old_thumbnail_id = get_blog_option($site_template->get_id(), 'template_img', false);
|
|
|
|
if ($old_thumbnail_id) {
|
|
$site_template->set_featured_image_id($old_thumbnail_id);
|
|
} else {
|
|
/*
|
|
* Try to get a custom screenshot previously taken.
|
|
*/
|
|
$image_url = $this->maybe_get_screenshot_url($site_template->get_id());
|
|
|
|
$attachment_id = \WP_Ultimo\Helpers\Screenshot::save_image_from_url($image_url);
|
|
|
|
if ($attachment_id) {
|
|
$site_template->set_featured_image_id($attachment_id);
|
|
} else {
|
|
/*
|
|
* If everything else fails, schedule a thumbnail to be saved.
|
|
*/
|
|
wu_enqueue_async_action(
|
|
'wu_async_take_screenshot',
|
|
[
|
|
'site_id' => $site_template->get_id(),
|
|
],
|
|
'site'
|
|
);
|
|
}
|
|
}
|
|
|
|
$saved = $site_template->save();
|
|
|
|
if (is_wp_error($saved)) {
|
|
throw new \Exception($saved->get_error_message());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert hard-coded thumbnail urls into v2 thumbnails.
|
|
*
|
|
* @since 2.0.0
|
|
*
|
|
* @param int $site_id The site template id.
|
|
* @return int|false
|
|
*/
|
|
protected function maybe_get_screenshot_url($site_id) {
|
|
|
|
$template = get_blog_details($site_id);
|
|
|
|
if ( ! $template) {
|
|
return false;
|
|
}
|
|
|
|
// Get filename
|
|
$filename = sanitize_title($template->siteurl);
|
|
$dir = wp_upload_dir();
|
|
|
|
// Check if exists
|
|
$filepath = $dir['basedir'] . '/' . $filename . '.jpg';
|
|
|
|
// Return URL
|
|
return file_exists($filepath) && 0 !== filesize($filepath) ? $dir['baseurl'] . '/' . $filename . '.jpg' : false;
|
|
}
|
|
|
|
/**
|
|
* Migrates domains.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_domains() {
|
|
|
|
global $wpdb;
|
|
|
|
/*
|
|
* Load dependencies.
|
|
*/
|
|
require_once wu_path('inc/functions/domain.php');
|
|
|
|
$wpdb->suppress_errors();
|
|
|
|
$domains = $wpdb->get_results(
|
|
"
|
|
SELECT
|
|
blog_id,
|
|
domain name,
|
|
active
|
|
FROM
|
|
{$wpdb->base_prefix}domain_mapping
|
|
"
|
|
);
|
|
|
|
$https = $this->get_old_setting('force_mapped_https', true);
|
|
|
|
foreach ($domains as $domain) {
|
|
$existing_domain = wu_get_domain_by_domain($domain->name);
|
|
|
|
if ($existing_domain) {
|
|
continue;
|
|
}
|
|
|
|
$domain = wu_create_domain(
|
|
[
|
|
'stage' => 'done',
|
|
'domain' => $domain->name,
|
|
'blog_id' => $domain->blog_id,
|
|
'active' => $domain->active,
|
|
'primary_domain' => true,
|
|
'secure' => $https,
|
|
]
|
|
);
|
|
|
|
if (is_wp_error($domain)) {
|
|
throw new \Exception($domain->get_error_message());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrates Checkout Forms.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_forms() {
|
|
/*
|
|
* Load dependencies.
|
|
*/
|
|
require_once wu_path('inc/deprecated/deprecated.php');
|
|
require_once wu_path('inc/functions/legacy.php');
|
|
require_once wu_path('inc/functions/checkout-form.php');
|
|
require_once wu_path('inc/functions/site.php');
|
|
|
|
/*
|
|
* Skip errors if a checkout form exists.
|
|
*/
|
|
if (wu_get_checkout_form_by_slug('main-form')) {
|
|
return;
|
|
}
|
|
|
|
$checkout_form = [
|
|
'name' => __('Signup Form', 'wp-ultimo'),
|
|
'slug' => 'main-form',
|
|
'allowed_countries' => $this->get_old_setting('allowed_countries', []),
|
|
'settings' => [],
|
|
];
|
|
|
|
$status = wu_create_checkout_form($checkout_form);
|
|
|
|
if (is_wp_error($status)) {
|
|
throw new \Exception($status->get_error_message());
|
|
} else {
|
|
$steps = Legacy_Checkout::get_instance()->get_steps();
|
|
|
|
$steps = Checkout_Form::convert_steps_to_v2($steps, $this->get_old_settings());
|
|
|
|
$status->set_settings($steps);
|
|
|
|
$status->save();
|
|
}
|
|
|
|
$post_content = '
|
|
<!-- wp:shortcode -->
|
|
[wu_checkout slug="%s"]
|
|
<!-- /wp:shortcode -->
|
|
';
|
|
|
|
/*
|
|
* Get post name based on setting for register page
|
|
*/
|
|
$page_slug = $this->get_old_setting('registration_url', 'register');
|
|
$page_slug = trim((string) $page_slug, '/');
|
|
|
|
/*
|
|
* Create the page on the main site.
|
|
*/
|
|
$post_details = [
|
|
'post_name' => $page_slug,
|
|
'post_title' => __('Signup', 'wp-ultimo'),
|
|
'post_status' => 'publish',
|
|
'post_type' => 'page',
|
|
'post_content' => sprintf($post_content, $status->get_slug()),
|
|
'post_author' => get_current_user_id(),
|
|
];
|
|
|
|
$page_id = wp_insert_post($post_details);
|
|
|
|
if (is_wp_error($page_id)) {
|
|
throw new \Exception($page_id->get_error_message());
|
|
}
|
|
|
|
/*
|
|
* Set the legacy template.
|
|
*/
|
|
update_post_meta($page_id, '_wp_page_template', 'signup-main.php');
|
|
|
|
/*
|
|
* Set page as the default registration page.
|
|
*/
|
|
wu_save_setting('default_registration_page', $page_id);
|
|
|
|
/*
|
|
* Get post name based on setting for login page
|
|
*/
|
|
$login_page_slug = $this->get_old_setting('login_url', false);
|
|
|
|
if ( ! $login_page_slug) {
|
|
return; // Bail if no login customization.
|
|
|
|
}
|
|
|
|
$login_page_slug = trim((string) $login_page_slug, '/');
|
|
|
|
/*
|
|
* Create the page on the main site.
|
|
*/
|
|
$login_post_details = [
|
|
'post_name' => $login_page_slug,
|
|
'post_title' => __('Login', 'wp-ultimo'),
|
|
'post_status' => 'publish',
|
|
'post_type' => 'page',
|
|
'post_content' => '',
|
|
'post_author' => get_current_user_id(),
|
|
];
|
|
|
|
$login_page_id = wp_insert_post($login_post_details);
|
|
|
|
if (is_wp_error($login_page_id)) {
|
|
throw new \Exception($login_page_id->get_error_message());
|
|
}
|
|
|
|
/*
|
|
* Set page as the default login page.
|
|
*/
|
|
wu_save_setting('default_login_page', $login_page_id);
|
|
|
|
wu_save_setting('enable_custom_login_page', true);
|
|
}
|
|
|
|
/**
|
|
* Migrates Emails.
|
|
*
|
|
* @todo Needs implementing.
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_emails() {
|
|
|
|
global $wpdb;
|
|
|
|
require_once wu_path('inc/functions/webhook.php');
|
|
require_once wu_path('inc/functions/broadcast.php');
|
|
|
|
/*
|
|
* Install the default emails.
|
|
*/
|
|
\WP_Ultimo\Managers\Email_Manager::get_instance()->create_all_system_emails();
|
|
|
|
$broadcasts = $wpdb->get_results(
|
|
"
|
|
SELECT
|
|
ID,
|
|
post_title,
|
|
post_content,
|
|
post_date,
|
|
post_modified
|
|
FROM
|
|
{$wpdb->base_prefix}posts
|
|
WHERE
|
|
post_type = 'wpultimo_broadcast'
|
|
"
|
|
);
|
|
|
|
foreach ($broadcasts as $broadcast) {
|
|
$existing_broadcast = wu_get_broadcast_by('migrated_from_id', $broadcast->ID);
|
|
|
|
if ($existing_broadcast) {
|
|
continue;
|
|
}
|
|
|
|
$old_type = get_post_meta($broadcast->ID, 'wpu_type', true);
|
|
|
|
$style = get_post_meta($broadcast->ID, 'wpu_style', true);
|
|
|
|
$new_type = 'message' === $old_type ? 'broadcast_notice' : 'broadcast_email';
|
|
|
|
$customer_targets = (array) get_post_meta($broadcast->ID, 'wpu_target_users', true);
|
|
$product_targets = (array) get_post_meta($broadcast->ID, 'wpu_target_plans', true);
|
|
|
|
$customer_targets = array_map(
|
|
function ($user_id) {
|
|
|
|
$customer = wu_get_customer_by_user_id($user_id);
|
|
|
|
if ($customer) {
|
|
return $customer->get_id();
|
|
}
|
|
|
|
return false;
|
|
},
|
|
$customer_targets
|
|
);
|
|
|
|
$product_targets = array_map(
|
|
function ($old_plan_id) {
|
|
|
|
$product = wu_get_product_by('migrated_from_id', $old_plan_id);
|
|
|
|
if ($product) {
|
|
return $product->get_id();
|
|
}
|
|
|
|
return false;
|
|
},
|
|
$product_targets
|
|
);
|
|
|
|
$targets = [
|
|
'customers' => array_filter($customer_targets),
|
|
'products' => array_filter($product_targets),
|
|
];
|
|
|
|
$broadcast = wu_create_broadcast(
|
|
[
|
|
'name' => $broadcast->post_title,
|
|
'content' => $broadcast->post_content,
|
|
'type' => $new_type,
|
|
'style' => $style ?: 'success',
|
|
'date_created' => $broadcast->post_date,
|
|
'date_modified' => $broadcast->post_modified,
|
|
'message_targets' => $targets,
|
|
'migrated_from_id' => $broadcast->ID,
|
|
'skip_validation' => true,
|
|
]
|
|
);
|
|
|
|
if (is_wp_error($broadcast)) {
|
|
throw new \Exception($broadcast->get_error_message());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrates Webhooks.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_webhooks() {
|
|
|
|
global $wpdb;
|
|
|
|
require_once wu_path('inc/functions/webhook.php');
|
|
|
|
$webhooks = $wpdb->get_results(
|
|
"
|
|
SELECT
|
|
ID,
|
|
post_title,
|
|
post_date,
|
|
post_modified
|
|
FROM
|
|
{$wpdb->base_prefix}posts
|
|
WHERE
|
|
post_type = 'wpultimo_webhook'
|
|
"
|
|
);
|
|
|
|
foreach ($webhooks as $webhook) {
|
|
$webhook = wu_create_webhook(
|
|
[
|
|
'name' => $webhook->post_title,
|
|
'migrated_from_id' => $webhook->ID,
|
|
'webhook_url' => get_post_meta($webhook->ID, 'wpu_url', true),
|
|
'event' => get_post_meta($webhook->ID, 'wpu_event', true),
|
|
'active' => (bool) get_post_meta($webhook->ID, 'wpu_active', true),
|
|
'date_created' => $webhook->post_date,
|
|
'date_modified' => $webhook->post_modified,
|
|
]
|
|
);
|
|
|
|
if (is_wp_error($webhook)) {
|
|
throw new \Exception($webhook->get_error_message());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrates other things.
|
|
*
|
|
* @since 2.0.0
|
|
* @throws \Exception Halts the process on error.
|
|
* @return void
|
|
*/
|
|
protected function _install_other() {
|
|
/*
|
|
* No need to run this one while checking.
|
|
*/
|
|
if ($this->dry_run) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Loads the integrations.
|
|
*/
|
|
Domain_Manager::get_instance()->load_integrations();
|
|
|
|
/*
|
|
* Get the list of integration classes.
|
|
*/
|
|
$integrations = Domain_Manager::get_instance()->get_integrations();
|
|
|
|
foreach ($integrations as $integration_class) {
|
|
/*
|
|
* Check if the class exists.
|
|
*/
|
|
if (class_exists($integration_class) === false) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Get the instance of the integration.
|
|
*/
|
|
$instance = $integration_class::get_instance();
|
|
|
|
if ($instance && $instance->is_setup()) {
|
|
$instance->enable();
|
|
}
|
|
}
|
|
}
|
|
}
|