class-wc-wccom-site-installation-manager.php
<?php
/**
* Installation Manager
*
* @package WooCommerce\WCCom
*/
use WC_REST_WCCOM_Site_Installer_Error_Codes as Installer_Error_Codes;
use WC_REST_WCCOM_Site_Installer_Error as Installer_Error;
defined( 'ABSPATH' ) || exit;
/**
* WC_WCCOM_Site_Installation_Manager class
*/
class WC_WCCOM_Site_Installation_Manager {
const STEPS = array(
'get_product_info',
'download_product',
'unpack_product',
'move_product',
'activate_product',
);
/**
* The product ID.
*
* @var int
*/
protected $product_id;
/**
* The idempotency key.
*
* @var string
*/
protected $idempotency_key;
/**
* Constructor.
*
* @param int $product_id The product ID.
* @param string $idempotency_key The idempotency key.
*/
public function __construct( int $product_id, string $idempotency_key ) {
$this->product_id = $product_id;
$this->idempotency_key = $idempotency_key;
}
/**
* Run the installation.
*
* @param string $run_until_step The step to run until.
* @return bool
* @throws WC_REST_WCCOM_Site_Installer_Error If installation failed to run.
*/
public function run_installation( string $run_until_step ): bool {
$state = WC_WCCOM_Site_Installation_State_Storage::get_state( $this->product_id );
if ( $state && $state->get_idempotency_key() !== $this->idempotency_key ) {
throw new Installer_Error( Installer_Error_Codes::IDEMPOTENCY_KEY_MISMATCH );
}
if ( ! $state ) {
$state = WC_WCCOM_Site_Installation_State::initiate_new( $this->product_id, $this->idempotency_key );
}
$this->can_run_installation( $run_until_step, $state );
$next_step = $this->get_next_step( $state );
$installation_steps = $this->get_installation_steps( $next_step, $run_until_step );
array_walk(
$installation_steps,
function ( $step_name ) use ( $state ) {
$this->run_step( $step_name, $state );
}
);
return true;
}
/**
* Get the next step to run.
*
* @return bool
* @throws WC_REST_WCCOM_Site_Installer_Error If the installation cannot be rest.
*/
public function reset_installation(): bool {
$state = WC_WCCOM_Site_Installation_State_Storage::get_state( $this->product_id );
if ( ! $state ) {
throw new Installer_Error( Installer_Error_Codes::NO_INITIATED_INSTALLATION_FOUND );
}
if ( $state->get_idempotency_key() !== $this->idempotency_key ) {
throw new Installer_Error( Installer_Error_Codes::IDEMPOTENCY_KEY_MISMATCH );
}
$result = WC_WCCOM_Site_Installation_State_Storage::delete_state( $state );
if ( ! $result ) {
throw new Installer_Error( Installer_Error_Codes::FAILED_TO_RESET_INSTALLATION_STATE );
}
return true;
}
/**
* Check if the installation can be run.
*
* @param string $run_until_step Run until this step.
* @param WC_WCCOM_Site_Installation_State $state Installation state.
* @return void
* @throws WC_REST_WCCOM_Site_Installer_Error If the installation cannot be run.
*/
protected function can_run_installation( $run_until_step, $state ) {
if ( $state->get_last_step_status() === \WC_WCCOM_Site_Installation_State::STEP_STATUS_IN_PROGRESS ) {
throw new Installer_Error( Installer_Error_Codes::INSTALLATION_ALREADY_RUNNING );
}
if ( $state->get_last_step_status() === \WC_WCCOM_Site_Installation_State::STEP_STATUS_FAILED ) {
throw new Installer_Error( Installer_Error_Codes::INSTALLATION_FAILED );
}
if ( $state->get_last_step_name() === self::STEPS[ count( self::STEPS ) - 1 ] ) {
throw new Installer_Error( Installer_Error_Codes::ALL_INSTALLATION_STEPS_RUN );
}
if ( array_search( $state->get_last_step_name(), self::STEPS, true ) >= array_search(
$run_until_step,
self::STEPS,
true
) ) {
throw new Installer_Error( Installer_Error_Codes::REQUESTED_STEP_ALREADY_RUN );
}
if ( ! is_writable( WP_CONTENT_DIR ) ) {
throw new Installer_Error( Installer_Error_Codes::FILESYSTEM_REQUIREMENTS_NOT_MET );
}
}
/**
* Get the next step to run.
*
* @param WC_WCCOM_Site_Installation_State $state Installation state.
* @return string
*/
protected function get_next_step( $state ): string {
$last_executed_step = $state->get_last_step_name();
if ( ! $last_executed_step ) {
return self::STEPS[0];
}
$last_executed_step_index = array_search( $last_executed_step, self::STEPS, true );
return self::STEPS[ $last_executed_step_index + 1 ];
}
/**
* Get the steps to run.
*
* @param string $start_step The step to start from.
* @param string $end_step The step to end at.
* @return string[]
*/
protected function get_installation_steps( string $start_step, string $end_step ) {
$start_step_offset = array_search( $start_step, self::STEPS, true );
$end_step_index = array_search( $end_step, self::STEPS, true );
$length = $end_step_index - $start_step_offset + 1;
return array_slice( self::STEPS, $start_step_offset, $length );
}
/**
* Run the step.
*
* @param string $step_name Step name.
* @param WC_WCCOM_Site_Installation_State $state Installation state.
* @return void
* @throws WC_REST_WCCOM_Site_Installer_Error If the step fails.
*/
protected function run_step( $step_name, $state ) {
$state->initiate_step( $step_name );
WC_WCCOM_Site_Installation_State_Storage::save_state( $state );
try {
$class_name = "WC_WCCOM_Site_Installation_Step_$step_name";
$current_step = new $class_name( $state );
$current_step->run();
} catch ( Installer_Error $exception ) {
$state->capture_failure( $step_name, $exception->get_error_code() );
WC_WCCOM_Site_Installation_State_Storage::save_state( $state );
throw $exception;
} catch ( Throwable $error ) {
$state->capture_failure( $step_name, Installer_Error_Codes::UNEXPECTED_ERROR );
WC_WCCOM_Site_Installation_State_Storage::save_state( $state );
throw new Installer_Error( Installer_Error_Codes::UNEXPECTED_ERROR, $error->getMessage() );
}
$state->complete_step( $step_name );
WC_WCCOM_Site_Installation_State_Storage::save_state( $state );
}
}