plugin install

This commit is contained in:
Tony Volpe
2024-06-18 17:29:05 -04:00
parent e1aaedd1ae
commit 41f50eacc4
5880 changed files with 1057631 additions and 39681 deletions

View File

@@ -0,0 +1,110 @@
<?php
/**
* WooCommerce.com Product Installation.
*
* @package WooCommerce\WCCom
* @since 3.7.0
*/
defined( 'ABSPATH' ) || exit;
/**
* WC_WCCOM_Site_Installer Class
*
* Contains functionalities to install products via WooCommerce.com helper connection.
*/
class WC_WCCOM_Site_Installer {
/**
* An instance of the WP_Upgrader class to be used for installation.
*
* @var \WP_Upgrader $wp_upgrader
*/
private static $wp_upgrader;
/**
* Get WP.org plugin's main file.
*
* @since 3.7.0
* @param string $dir Directory name of the plugin.
* @return bool|string
*/
public static function get_wporg_plugin_main_file( $dir ) {
// Ensure that exact dir name is used.
$dir = trailingslashit( $dir );
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugins = get_plugins();
foreach ( $plugins as $path => $plugin ) {
if ( 0 === strpos( $path, $dir ) ) {
return $path;
}
}
return false;
}
/**
* Get plugin info
*
* @since 3.9.0
* @param string $dir Directory name of the plugin.
* @return bool|array
*/
public static function get_plugin_info( $dir ) {
$plugin_folder = basename( $dir );
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugins = get_plugins();
$related_plugins = array_filter(
$plugins,
function( $key ) use ( $plugin_folder ) {
return strpos( $key, $plugin_folder . '/' ) === 0;
},
ARRAY_FILTER_USE_KEY
);
if ( 1 === count( $related_plugins ) ) {
$plugin_key = array_keys( $related_plugins )[0];
$plugin_data = $plugins[ $plugin_key ];
return array(
'name' => $plugin_data['Name'],
'version' => $plugin_data['Version'],
'active' => is_plugin_active( $plugin_key ),
);
}
return false;
}
/**
* Get an instance of WP_Upgrader to use for installing plugins.
*
* @return WP_Upgrader
*/
public static function get_wp_upgrader() {
if ( ! empty( self::$wp_upgrader ) ) {
return self::$wp_upgrader;
}
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/plugin.php';
WP_Filesystem();
self::$wp_upgrader = new WP_Upgrader( new Automatic_Upgrader_Skin() );
self::$wp_upgrader->init();
wp_clean_plugins_cache();
return self::$wp_upgrader;
}
}

View File

@@ -0,0 +1,249 @@
<?php
/**
* WooCommerce.com Product Installation.
*
* @package WooCommerce\WCCom
* @since 3.7.0
*/
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 Class
*
* Main class for WooCommerce.com connected site.
*/
class WC_WCCOM_Site {
const AUTH_ERROR_FILTER_NAME = 'wccom_auth_error';
/**
* Load the WCCOM site class.
*
* @since 3.7.0
*/
public static function load() {
self::includes();
add_action( 'woocommerce_wccom_install_products', array( 'WC_WCCOM_Site_Installer', 'install' ) );
add_filter( 'determine_current_user', array( __CLASS__, 'authenticate_wccom' ), 14 );
add_action( 'woocommerce_rest_api_get_rest_namespaces', array( __CLASS__, 'register_rest_namespace' ) );
}
/**
* Include support files.
*
* @since 3.7.0
*/
protected static function includes() {
require_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper.php';
require_once WC_ABSPATH . 'includes/wccom-site/class-wc-wccom-site-installer.php';
}
/**
* Authenticate WooCommerce.com request.
*
* @since 3.7.0
* @param int|false $user_id User ID.
* @return int|false
*/
public static function authenticate_wccom( $user_id ) {
if ( ! empty( $user_id ) || ! self::is_request_to_wccom_site_rest_api() ) {
return $user_id;
}
$auth_header = trim( self::get_authorization_header() );
if ( stripos( $auth_header, 'Bearer ' ) === 0 ) {
$access_token = trim( substr( $auth_header, 7 ) );
} elseif ( ! empty( $_GET['token'] ) && is_string( $_GET['token'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$access_token = trim( $_GET['token'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
} else {
add_filter(
self::AUTH_ERROR_FILTER_NAME,
function() {
return new Installer_Error( Installer_Error_Codes::NO_ACCESS_TOKEN );
}
);
return false;
}
if ( ! empty( $_SERVER['HTTP_X_WOO_SIGNATURE'] ) ) {
$signature = trim( $_SERVER['HTTP_X_WOO_SIGNATURE'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
} elseif ( ! empty( $_GET['signature'] ) && is_string( $_GET['signature'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$signature = trim( $_GET['signature'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
} else {
add_filter(
self::AUTH_ERROR_FILTER_NAME,
function() {
return new Installer_Error( Installer_Error_Codes::NO_SIGNATURE );
}
);
return false;
}
require_once WC_ABSPATH . 'includes/admin/helper/class-wc-helper-options.php';
$site_auth = WC_Helper_Options::get( 'auth' );
if ( empty( $site_auth['access_token'] ) ) {
add_filter(
self::AUTH_ERROR_FILTER_NAME,
function() {
return new Installer_Error( Installer_Error_Codes::SITE_NOT_CONNECTED );
}
);
return false;
}
if ( ! hash_equals( $access_token, $site_auth['access_token'] ) ) {
add_filter(
self::AUTH_ERROR_FILTER_NAME,
function() {
return new Installer_Error( Installer_Error_Codes::INVALID_TOKEN );
}
);
return false;
}
$body = WP_REST_Server::get_raw_data();
if ( ! self::verify_wccom_request( $body, $signature, $site_auth['access_token_secret'] ) ) {
add_filter(
self::AUTH_ERROR_FILTER_NAME,
function() {
return new Installer_Error( Installer_Error_Codes::REQUEST_VERIFICATION_FAILED );
}
);
return false;
}
$user = get_user_by( 'id', $site_auth['user_id'] );
if ( ! $user ) {
add_filter(
self::AUTH_ERROR_FILTER_NAME,
function() {
return new Installer_Error( Installer_Error_Codes::USER_NOT_FOUND );
}
);
return false;
}
return $user;
}
/**
* Get the authorization header.
*
* On certain systems and configurations, the Authorization header will be
* stripped out by the server or PHP. Typically this is then used to
* generate `PHP_AUTH_USER`/`PHP_AUTH_PASS` but not passed on. We use
* `getallheaders` here to try and grab it out instead.
*
* @since 3.7.0
* @return string Authorization header if set.
*/
protected static function get_authorization_header() {
if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
}
if ( function_exists( 'getallheaders' ) ) {
$headers = getallheaders();
// Check for the authoization header case-insensitively.
foreach ( $headers as $key => $value ) {
if ( 'authorization' === strtolower( $key ) ) {
return $value;
}
}
}
return '';
}
/**
* Check if this is a request to WCCOM Site REST API.
*
* @since 3.7.0
* @return bool
*/
protected static function is_request_to_wccom_site_rest_api() {
if ( isset( $_REQUEST['rest_route'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$route = wp_unslash( $_REQUEST['rest_route'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
$rest_prefix = '';
} else {
$route = wp_unslash( add_query_arg( array() ) );
$rest_prefix = trailingslashit( rest_get_url_prefix() );
}
return false !== strpos( $route, $rest_prefix . 'wccom-site/' );
}
/**
* Verify WooCommerce.com request from a given body and signature request.
*
* @since 3.7.0
* @param string $body Request body.
* @param string $signature Request signature found in X-Woo-Signature header.
* @param string $access_token_secret Access token secret for this site.
* @return bool
*/
protected static function verify_wccom_request( $body, $signature, $access_token_secret ) {
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$data = array(
'host' => $_SERVER['HTTP_HOST'],
'request_uri' => urldecode( remove_query_arg( array( 'token', 'signature' ), $_SERVER['REQUEST_URI'] ) ),
'method' => strtoupper( $_SERVER['REQUEST_METHOD'] ),
);
// phpcs:enable
if ( ! empty( $body ) ) {
$data['body'] = $body;
}
$expected_signature = hash_hmac( 'sha256', wp_json_encode( $data ), $access_token_secret );
return hash_equals( $expected_signature, $signature );
}
/**
* Register wccom-site REST namespace.
*
* @since 3.7.0
* @param array $namespaces List of registered namespaces.
* @return array Registered namespaces.
*/
public static function register_rest_namespace( $namespaces ) {
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/class-wc-rest-wccom-site-installer-error-codes.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/class-wc-rest-wccom-site-installer-error.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/abstract-wc-rest-wccom-site-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-installer-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-ssr-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-status-controller.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/class-wc-wccom-site-installation-state.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/class-wc-wccom-site-installation-state-storage.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/class-wc-wccom-site-installation-manager.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/installation-steps/interface-installaton-step.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-get-product-info.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-download-product.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-unpack-product.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-move-product.php';
require_once WC_ABSPATH . 'includes/wccom-site/installation/installation-steps/class-wc-wccom-site-installation-step-activate-product.php';
$namespaces['wccom-site/v2'] = array(
'installer' => 'WC_REST_WCCOM_Site_Installer_Controller',
'ssr' => 'WC_REST_WCCOM_Site_SSR_Controller',
'status' => 'WC_REST_WCCOM_Site_Status_Controller',
);
return $namespaces;
}
}
WC_WCCOM_Site::load();

View File

@@ -0,0 +1,208 @@
<?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 );
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* State storage for the WCCOM Site installation process.
*
* @package WooCommerce\WCCOM
*/
defined( 'ABSPATH' ) || exit;
/**
* WC_WCCOM_Site_Installation_State_Storage class
*/
class WC_WCCOM_Site_Installation_State_Storage {
/**
* Get state from storage.
*
* @param int $product_id The product ID.
* @return WC_WCCOM_Site_Installation_State|null
*/
public static function get_state( $product_id ) : ?WC_WCCOM_Site_Installation_State {
$storage_key = self::get_storage_key( $product_id );
$data = get_option( $storage_key );
if ( ! is_array( $data ) ) {
return null;
}
$installation_state = WC_WCCOM_Site_Installation_State::initiate_existing(
$product_id,
$data['idempotency_key'],
$data['last_step_name'],
$data['last_step_status'],
$data['last_step_error'],
$data['started_date']
);
$installation_state->set_product_type( $data['product_type'] ?? null );
$installation_state->set_product_name( $data['product_name'] ?? null );
$installation_state->set_download_url( $data['download_url'] ?? null );
$installation_state->set_download_path( $data['download_path'] ?? null );
$installation_state->set_unpacked_path( $data['unpacked_path'] ?? null );
$installation_state->set_installed_path( $data['installed_path'] ?? null );
$installation_state->set_already_installed_plugin_info( $data['already_installed_plugin_info'] ?? null );
return $installation_state;
}
/**
* Save state to storage.
*
* @param WC_WCCOM_Site_Installation_State $state The state to save.
* @return bool
*/
public static function save_state( WC_WCCOM_Site_Installation_State $state ) : bool {
$storage_key = self::get_storage_key( $state->get_product_id() );
return update_option(
$storage_key,
array(
'product_id' => $state->get_product_id(),
'idempotency_key' => $state->get_idempotency_key(),
'last_step_name' => $state->get_last_step_name(),
'last_step_status' => $state->get_last_step_status(),
'last_step_error' => $state->get_last_step_error(),
'product_type' => $state->get_product_type(),
'product_name' => $state->get_product_name(),
'download_url' => $state->get_download_url(),
'download_path' => $state->get_download_path(),
'unpacked_path' => $state->get_unpacked_path(),
'installed_path' => $state->get_installed_path(),
'already_installed_plugin_info' => $state->get_already_installed_plugin_info(),
'started_date' => $state->get_started_date(),
)
);
}
/**
* Delete state from storage.
*
* @param WC_WCCOM_Site_Installation_State $state The state to delete.
* @return bool
*/
public static function delete_state( WC_WCCOM_Site_Installation_State $state ) : bool {
$storage_key = self::get_storage_key( $state->get_product_id() );
return delete_option( $storage_key );
}
/**
* Get the storage key for a product ID.
*
* @param int $product_id The product ID.
* @return string
*/
protected static function get_storage_key( $product_id ) : string {
return sprintf( 'wccom-product-installation-state-%d', $product_id );
}
}

View File

@@ -0,0 +1,343 @@
<?php
/**
* State for the WCCOM Site installation process.
*
* @package WooCommerce\WCCOM
*/
defined( 'ABSPATH' ) || exit;
/**
* WC_WCCOM_Site_Installation_State class
*/
class WC_WCCOM_Site_Installation_State {
/**
* The product ID.
*
* @var string
*/
protected $product_id;
/**
* The idempotency key.
*
* @var string
*/
protected $idempotency_key;
/**
* The last step name.
*
* @var string
*/
protected $last_step_name;
/**
* The last step status.
*
* @var string
*/
protected $last_step_status;
/**
* The last step error.
*
* @var string
*/
protected $last_step_error;
/**
* The product type.
*
* @var string
*/
protected $product_type;
/**
* The product name.
*
* @var string
*/
protected $product_name;
/**
* The product slug.
*
* @var string
*/
protected $download_url;
/**
* The path to the downloaded file.
*
* @var string
*/
protected $download_path;
/**
* The path to the unpacked file.
*
* @var string
*/
protected $unpacked_path;
/**
* The path to the installed file.
*
* @var string
*/
protected $installed_path;
/**
* The plugin info for the already installed plugin.
*
* @var array
*/
protected $already_installed_plugin_info;
/**
* The timestamp of the installation start.
*
* @var int
*/
protected $started_date;
const STEP_STATUS_IN_PROGRESS = 'in-progress';
const STEP_STATUS_FAILED = 'failed';
const STEP_STATUS_COMPLETED = 'completed';
/**
* Constructor.
*
* @param string $product_id The product ID.
*/
protected function __construct( $product_id ) {
$this->product_id = $product_id;
}
/**
* Initiate an existing installation state.
*
* @param int $product_id The product ID.
* @param string $idempotency_key The idempotency key.
* @param string $last_step_name The last step name.
* @param string $last_step_status The last step status.
* @param string $last_step_error The last step error.
* @param int $started_date The timestamp of the installation start.
* @return WC_WCCOM_Site_Installation_State The instance.
*/
public static function initiate_existing( $product_id, $idempotency_key, $last_step_name, $last_step_status, $last_step_error, $started_date ) {
$instance = new self( $product_id );
$instance->idempotency_key = $idempotency_key;
$instance->last_step_name = $last_step_name;
$instance->last_step_status = $last_step_status;
$instance->last_step_error = $last_step_error;
$instance->started_date = $started_date;
return $instance;
}
/**
* Initiate a new installation state.
*
* @param init $product_id The product ID.
* @param string $idempotency_key The idempotency key.
* @return WC_WCCOM_Site_Installation_State The instance.
*/
public static function initiate_new( $product_id, $idempotency_key ) {
$instance = new self( $product_id );
$instance->idempotency_key = $idempotency_key;
$instance->started_date = time();
return $instance;
}
/**
* Get the product ID.
*
* @return string
*/
public function get_product_id() {
return $this->product_id;
}
/**
* Get the idempotency key.
*
* @return string
*/
public function get_idempotency_key() {
return $this->idempotency_key;
}
/**
* Get the timestamp of the installation start.
*
* @return int
*/
public function get_last_step_name() {
return $this->last_step_name;
}
/**
* Get the last step status.
*
* @return string
*/
public function get_last_step_status() {
return $this->last_step_status;
}
/**
* Get the last step error.
*
* @return int
*/
public function get_last_step_error() {
return $this->last_step_error;
}
/**
* Initiate a step.
*
* @param string $step_name Step name.
* @return void
*/
public function initiate_step( $step_name ) {
$this->last_step_name = $step_name;
$this->last_step_status = self::STEP_STATUS_IN_PROGRESS;
}
/**
* Capture a successful installation of a step.
*
* @param string $step_name The step name.
*/
public function complete_step( $step_name ) {
$this->last_step_name = $step_name;
$this->last_step_status = self::STEP_STATUS_COMPLETED;
}
/**
* Capture an installation failure.
*
* @param string $step_name The step name.
* @param string $error_code The error code.
*/
public function capture_failure( $step_name, $error_code ) {
$this->last_step_name = $step_name;
$this->last_step_error = $error_code;
$this->last_step_status = self::STEP_STATUS_FAILED;
}
/**
* Get the product type.
*
* @return string
*/
public function get_product_type() {
return $this->product_type;
}
/**
* Set the product type.
*
* @param string $product_type The product type.
*/
public function set_product_type( $product_type ) {
$this->product_type = $product_type;
}
/**
* Get the product name.
*
* @return string
*/
public function get_product_name() {
return $this->product_name;
}
/**
* Set the product name.
*
* @param string $product_name The product name.
*/
public function set_product_name( $product_name ) {
$this->product_name = $product_name;
}
/**
* Get the download URL.
*
* @return string
*/
public function get_download_url() {
return $this->download_url;
}
/**
* Set the download URL.
*
* @param string $download_url The download URL.
*/
public function set_download_url( $download_url ) {
$this->download_url = $download_url;
}
/**
* Get the path to the downloaded file.
*
* @return string
*/
public function get_download_path() {
return $this->download_path;
}
/**
* Set the path to the downloaded file.
*
* @param string $download_path The path to the downloaded file.
*/
public function set_download_path( $download_path ) {
$this->download_path = $download_path;
}
/**
* Get the path to the unpacked file.
*
* @return string
*/
public function get_unpacked_path() {
return $this->unpacked_path;
}
/**
* Set the path to the unpacked file.
*
* @param string $unpacked_path The path to the unpacked file.
*/
public function set_unpacked_path( $unpacked_path ) {
$this->unpacked_path = $unpacked_path;
}
/**
* Get the path to the installed file.
*
* @return string
*/
public function get_installed_path() {
return $this->installed_path;
}
/**
* Set the path to the installed file.
*
* @param string $installed_path The path to the installed file.
*/
public function set_installed_path( $installed_path ) {
$this->installed_path = $installed_path;
}
/**
* Get the plugin info for the already installed plugin.
*
* @return array
*/
public function get_already_installed_plugin_info() {
return $this->already_installed_plugin_info;
}
/**
* Set the plugin info for the already installed plugin.
*
* @param array $plugin_info The plugin info.
*/
public function set_already_installed_plugin_info( $plugin_info ) {
$this->already_installed_plugin_info = $plugin_info;
}
/**
* Get the timestamp of the installation start.
*
* @return int
*/
public function get_started_date() {
return $this->started_date;
}
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* Activate product step.
*
* @package WooCommerce\WCCom
* @since 7.7.0
*/
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_Step_Activate_Product Class
*/
class WC_WCCOM_Site_Installation_Step_Activate_Product implements WC_WCCOM_Site_Installation_Step {
/**
* The current installation state.
*
* @var WC_WCCOM_Site_Installation_State
*/
protected $state;
/**
* Constructor.
*
* @param array $state The current installation state.
*/
public function __construct( $state ) {
$this->state = $state;
}
/**
* Run the step installation process.
*/
public function run() {
$product_id = $this->state->get_product_id();
if ( 'plugin' === $this->state->get_product_type() ) {
$this->activate_plugin( $product_id );
} else {
$this->activate_theme( $product_id );
}
return $this->state;
}
/**
* Activate plugin.
*
* @param int $product_id Product ID.
* @return void
* @throws WC_REST_WCCOM_Site_Installer_Error If plugin activation failed.
*/
private function activate_plugin( $product_id ) {
// Clear plugins cache used in `WC_Helper::get_local_woo_plugins`.
wp_clean_plugins_cache();
$filename = false;
// If product is WP.org one, find out its filename.
$dir_name = $this->get_wporg_product_dir_name();
if ( false !== $dir_name ) {
$filename = \WC_WCCOM_Site_Installer::get_wporg_plugin_main_file( $dir_name );
}
if ( false === $filename ) {
$plugins = wp_list_filter(
WC_Helper::get_local_woo_plugins(),
array(
'_product_id' => $product_id,
)
);
$filename = is_array( $plugins ) && ! empty( $plugins ) ? key( $plugins ) : '';
}
if ( empty( $filename ) ) {
throw new Installer_Error( Installer_Error_Codes::UNKNOWN_FILENAME );
}
// If the plugin is already active, make sure we call the registration hook.
if ( is_plugin_active( $filename ) ) {
WC_Helper::activated_plugin( $filename );
}
$result = activate_plugin( $filename );
if ( is_wp_error( $result ) ) {
throw new Installer_Error( Installer_Error_Codes::PLUGIN_ACTIVATION_ERROR, $result->get_error_message() );
}
}
/**
* Activate theme.
*
* @param int $product_id Product ID.
* @return void
* @throws WC_REST_WCCOM_Site_Installer_Error If theme activation failed.
*/
private function activate_theme( $product_id ) {
// Clear plugins cache used in `WC_Helper::get_local_woo_themes`.
wp_clean_themes_cache();
$theme_slug = false;
// If product is WP.org theme, find out its slug.
$dir_name = $this->get_wporg_product_dir_name();
if ( false !== $dir_name ) {
$theme_slug = basename( $dir_name );
}
if ( false === $theme_slug ) {
$themes = wp_list_filter(
WC_Helper::get_local_woo_themes(),
array(
'_product_id' => $product_id,
)
);
$theme_slug = is_array( $themes ) && ! empty( $themes ) ? dirname( key( $themes ) ) : '';
}
if ( empty( $theme_slug ) ) {
throw new Installer_Error( Installer_Error_Codes::UNKNOWN_FILENAME );
}
switch_theme( $theme_slug );
}
/**
* Get WP.org product directory name.
*
* @return string|false
*/
private function get_wporg_product_dir_name() {
if ( empty( $this->state->get_installed_path() ) ) {
return false;
}
// Check whether product was downloaded from WordPress.org.
$download_url = $this->state->get_download_url();
$parsed_url = wp_parse_url( $download_url );
if ( ! empty( $parsed_url['host'] ) && 'downloads.wordpress.org' !== $parsed_url['host'] ) {
return false;
}
return basename( $this->state->get_installed_path() );
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Download product step.
*
* @package WooCommerce\WCCom
* @since 7.7.0
*/
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_Step_Download_Product class
*/
class WC_WCCOM_Site_Installation_Step_Download_Product implements WC_WCCOM_Site_Installation_Step {
/**
* The current installation state.
*
* @var WC_WCCOM_Site_Installation_State
*/
protected $state;
/**
* Constructor.
*
* @param array $state The current installation state.
*/
public function __construct( $state ) {
$this->state = $state;
}
/**
* Run the step installation process.
*
* @throws Installer_Error Installer Error.
*/
public function run() {
$upgrader = WC_WCCOM_Site_Installer::get_wp_upgrader();
$download_path = $upgrader->download_package( $this->state->get_download_url() );
if ( empty( $download_path ) ) {
throw new Installer_Error( Installer_Error_Codes::MISSING_DOWNLOAD_PATH );
}
$this->state->set_download_path( $download_path );
return $this->state;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* Get product info step.
*
* @package WooCommerce\WCCom
* @since 7.7.0
*/
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_Step_Get_Product_Info class
*/
class WC_WCCOM_Site_Installation_Step_Get_Product_Info implements WC_WCCOM_Site_Installation_Step {
/**
* The current installation state.
*
* @var WC_WCCOM_Site_Installation_State
*/
protected $state;
/**
* Constructor.
*
* @param array $state The current installation state.
*/
public function __construct( $state ) {
$this->state = $state;
}
/**
* Run the step installation process.
*
* @throws Installer_Error Installer Error.
* @return array
*/
public function run() {
$product_id = $this->state->get_product_id();
// Get product info from WooCommerce.com.
$request = WC_Helper_API::get(
add_query_arg(
array( 'product_id' => $product_id ),
'info'
),
array(
'authenticated' => true,
)
);
if ( 200 !== wp_remote_retrieve_response_code( $request ) ) {
throw new Installer_Error( Installer_Error_Codes::FAILED_GETTING_PRODUCT_INFO );
}
$result = json_decode( wp_remote_retrieve_body( $request ), true );
if ( ! isset( $result['_product_type'], $result['name'] ) ) {
throw new Installer_Error( Installer_Error_Codes::INVALID_PRODUCT_INFO_RESPONSE );
}
if ( ! empty( $result['_wporg_product'] ) ) {
$download_url = $this->get_wporg_download_url( $result );
} else {
$download_url = $this->get_wccom_download_url( $product_id );
}
$this->state->set_product_type( $result['_product_type'] );
$this->state->set_product_name( $result['name'] );
$this->state->set_download_url( $download_url );
return $this->state;
}
/**
* Get download URL for wporg product.
*
* @param array $data Product data.
*
* @return string|null
* @throws Installer_Error Installer Error.
*/
protected function get_wporg_download_url( $data ) {
if ( empty( $data['_wporg_product'] ) ) {
return null;
}
if ( empty( $data['download_link'] ) ) {
throw new Installer_Error( Installer_Error_Codes::WPORG_PRODUCT_MISSING_DOWNLOAD_LINK );
}
return $data['download_link'];
}
/**
* Get download URL for wccom product.
*
* @param int $product_id Product ID.
*
* @return string
* @throws Installer_Error Installer Error.
*/
protected function get_wccom_download_url( $product_id ) {
WC_Helper::_flush_subscriptions_cache();
if ( ! WC_Helper::has_product_subscription( $product_id ) ) {
throw new Installer_Error( Installer_Error_Codes::WCCOM_PRODUCT_MISSING_SUBSCRIPTION );
}
// Retrieve download URL for non-wporg product.
WC_Helper_Updater::flush_updates_cache();
$updates = WC_Helper_Updater::get_update_data();
if ( empty( $updates[ $product_id ]['package'] ) ) {
throw new Installer_Error( Installer_Error_Codes::WCCOM_PRODUCT_MISSING_PACKAGE );
}
return $updates[ $product_id ]['package'];
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* Move product to the correct location.
*
* @package WooCommerce\WCCom
* @since 7.7.0
*/
defined( 'ABSPATH' ) || exit;
/**
* WC_WCCOM_Site_Installation_Step_Move_Product class
*/
class WC_WCCOM_Site_Installation_Step_Move_Product implements WC_WCCOM_Site_Installation_Step {
/**
* The current installation state.
*
* @var WC_WCCOM_Site_Installation_State
*/
protected $state;
/**
* Constructor.
*
* @param array $state The current installation state.
*/
public function __construct( $state ) {
$this->state = $state;
}
/**
* Run the step installation process.
*/
public function run() {
$upgrader = WC_WCCOM_Site_Installer::get_wp_upgrader();
$destination = 'plugin' === $this->state->get_product_type()
? WP_PLUGIN_DIR
: get_theme_root();
$package = array(
'source' => $this->state->get_unpacked_path(),
'destination' => $destination,
'clear_working' => true,
'hook_extra' => array(
'type' => $this->state->get_product_type(),
'action' => 'install',
),
);
$result = $upgrader->install_package( $package );
/**
* If install package returns error 'folder_exists' treat as success.
*/
if ( is_wp_error( $result ) && array_key_exists( 'folder_exists', $result->errors ) ) {
$existing_folder_path = $result->error_data['folder_exists'];
$plugin_info = WC_WCCOM_Site_Installer::get_plugin_info( $existing_folder_path );
$this->state->set_installed_path( $existing_folder_path );
$this->state->set_already_installed_plugin_info( $plugin_info );
return $this->state;
}
$this->state->set_installed_path( $result['destination'] );
return $this->state;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Get product info step.
*
* @package WooCommerce\WCCom
* @since 7.7.0
*/
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_Step_Unpack_Product class
*/
class WC_WCCOM_Site_Installation_Step_Unpack_Product implements WC_WCCOM_Site_Installation_Step {
/**
* The current installation state.
*
* @var WC_WCCOM_Site_Installation_State
*/
protected $state;
/**
* Constructor.
*
* @param array $state The current installation state.
*/
public function __construct( $state ) {
$this->state = $state;
}
/**
* Run the step installation process.
*
* @return WC_WCCOM_Site_Installation_State
* @throws WC_REST_WCCOM_Site_Installer_Error If the package unpacked path is not returned.
*/
public function run() {
$upgrader = WC_WCCOM_Site_Installer::get_wp_upgrader();
$unpacked_path = $upgrader->unpack_package( $this->state->get_download_path(), true );
if ( empty( $unpacked_path ) ) {
throw new Installer_Error( Installer_Error_Codes::MISSING_UNPACKED_PATH );
}
$this->state->set_unpacked_path( $unpacked_path );
return $this->state;
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* Interface for installation steps.
*
* @package WooCommerce\WCCom
* @since 7.7.0
*/
defined( 'ABSPATH' ) || exit;
interface WC_WCCOM_Site_Installation_Step {
/**
* Constructor.
*
* @param array $state The current installation state.
*/
public function __construct( $state );
/**
* Run the step installation process.
*/
public function run();
}

View File

@@ -0,0 +1,90 @@
<?php
/**
* WCCOM Site Installer Error Codes Class
*
* @package WooCommerce\WCCom\API
* @since 7.7.0
*/
defined( 'ABSPATH' ) || exit;
/**
* WCCOM Site Installer Error Codes Class
*
* Stores data for errors, returned by installer API.
*/
class WC_REST_WCCOM_Site_Installer_Error_Codes {
const NOT_AUTHENTICATED = 'not_authenticated';
const NO_ACCESS_TOKEN = 'no_access_token';
const NO_SIGNATURE = 'no_signature';
const SITE_NOT_CONNECTED = 'site_not_connnected';
const INVALID_TOKEN = 'invalid_token';
const REQUEST_VERIFICATION_FAILED = 'request_verification_failed';
const USER_NOT_FOUND = 'user_not_found';
const NO_PERMISSION = 'forbidden';
const IDEMPOTENCY_KEY_MISMATCH = 'idempotency_key_mismatch';
const NO_INITIATED_INSTALLATION_FOUND = 'no_initiated_installation_found';
const ALL_INSTALLATION_STEPS_RUN = 'all_installation_steps_run';
const REQUESTED_STEP_ALREADY_RUN = 'requested_step_already_run';
const PLUGIN_ALREADY_INSTALLED = 'plugin_already_installed';
const INSTALLATION_ALREADY_RUNNING = 'installation_already_running';
const INSTALLATION_FAILED = 'installation_failed';
const FILESYSTEM_REQUIREMENTS_NOT_MET = 'filesystem_requirements_not_met';
const FAILED_GETTING_PRODUCT_INFO = 'product_info_failed';
const INVALID_PRODUCT_INFO_RESPONSE = 'invalid_product_info_response';
const WCCOM_PRODUCT_MISSING_SUBSCRIPTION = 'wccom_product_missing_subscription';
const WCCOM_PRODUCT_MISSING_PACKAGE = 'wccom_product_missing_package';
const WPORG_PRODUCT_MISSING_DOWNLOAD_LINK = 'wporg_product_missing_download_link';
const MISSING_DOWNLOAD_PATH = 'missing_download_path';
const MISSING_UNPACKED_PATH = 'missing_unpacked_path';
const UNKNOWN_FILENAME = 'unknown_filename';
const PLUGIN_ACTIVATION_ERROR = 'plugin_activation_error';
const UNEXPECTED_ERROR = 'unexpected_error';
const FAILED_TO_RESET_INSTALLATION_STATE = 'failed_to_reset_installation_state';
const ERROR_MESSAGES = array(
self::NOT_AUTHENTICATED => 'Authentication required',
self::NO_ACCESS_TOKEN => 'No access token provided',
self::NO_SIGNATURE => 'No signature provided',
self::SITE_NOT_CONNECTED => 'Site not connected to WooCommerce.com',
self::INVALID_TOKEN => 'Invalid access token provided',
self::REQUEST_VERIFICATION_FAILED => 'Request verification by signature failed',
self::USER_NOT_FOUND => 'Token owning user not found',
self::NO_PERMISSION => 'You do not have permission to install plugin or theme',
self::IDEMPOTENCY_KEY_MISMATCH => 'Idempotency key mismatch',
self::NO_INITIATED_INSTALLATION_FOUND => 'No initiated installation for the product found',
self::ALL_INSTALLATION_STEPS_RUN => 'All installation steps have been run',
self::REQUESTED_STEP_ALREADY_RUN => 'Requested step has already been run',
self::PLUGIN_ALREADY_INSTALLED => 'The plugin has already been installed',
self::INSTALLATION_ALREADY_RUNNING => 'The installation of the plugin is already running',
self::INSTALLATION_FAILED => 'The installation of the plugin failed',
self::FILESYSTEM_REQUIREMENTS_NOT_MET => 'The filesystem requirements are not met',
self::FAILED_GETTING_PRODUCT_INFO => 'Failed to retrieve product info from WooCommerce.com',
self::INVALID_PRODUCT_INFO_RESPONSE => 'Invalid product info response from WooCommerce.com',
self::WCCOM_PRODUCT_MISSING_SUBSCRIPTION => 'Product subscription is missing',
self::WCCOM_PRODUCT_MISSING_PACKAGE => 'Could not find product package',
self::MISSING_DOWNLOAD_PATH => 'Download path is missing',
self::MISSING_UNPACKED_PATH => 'Unpacked path is missing',
self::UNKNOWN_FILENAME => 'Unknown product filename',
self::PLUGIN_ACTIVATION_ERROR => 'Plugin activation error',
self::UNEXPECTED_ERROR => 'Unexpected error',
self::FAILED_TO_RESET_INSTALLATION_STATE => 'Failed to reset installation state',
);
const HTTP_CODES = array(
self::NOT_AUTHENTICATED => 401,
self::NO_ACCESS_TOKEN => 400,
self::NO_SIGNATURE => 400,
self::SITE_NOT_CONNECTED => 401,
self::INVALID_TOKEN => 401,
self::REQUEST_VERIFICATION_FAILED => 400,
self::USER_NOT_FOUND => 401,
self::NO_PERMISSION => 403,
self::IDEMPOTENCY_KEY_MISMATCH => 400,
self::NO_INITIATED_INSTALLATION_FOUND => 400,
self::ALL_INSTALLATION_STEPS_RUN => 400,
self::REQUESTED_STEP_ALREADY_RUN => 400,
self::UNEXPECTED_ERROR => 500,
);
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* WCCOM Site Installer Error Class
*
* @package WooCommerce\WCCom\API
* @since 7.7.0
*/
defined( 'ABSPATH' ) || exit;
/**
* WCCOM Site Installer Error Class
*/
class WC_REST_WCCOM_Site_Installer_Error extends Exception {
/**
* Constructor for the Installer Error class.
*
* @param string $error_code Error code.
* @param string $error_message Error message.
* @param int $http_code HTTP status code.
*/
public function __construct( $error_code, $error_message = null, $http_code = null ) {
$this->error_code = $error_code;
$this->error_message = $error_message ?? WC_REST_WCCOM_Site_Installer_Error_Codes::ERROR_MESSAGES[ $error_code ] ?? '';
$this->http_code = $http_code ?? WC_REST_WCCOM_Site_Installer_Error_Codes::HTTP_CODES[ $error_code ] ?? 400;
parent::__construct( $error_code );
}
/**
* Get the error code.
*/
public function get_error_code() {
return $this->error_code;
}
/**
* Get the error message.
*/
public function get_error_message() {
return $this->error_message;
}
/**
* Get the HTTP status code.
*/
public function get_http_code() {
return $this->http_code;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* WCCOM Site Base REST API Controller
*
* Handles requests to /ssr.
*
* @package WooCommerce\WCCom\API
* @since 8.6.0
*/
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;
/**
* REST API WCCOM Site Base REST API Controller Astract Class.
*
* @extends WC_REST_Controller
*/
abstract class WC_REST_WCCOM_Site_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wccom-site/v3';
/**
* Check whether user has permission to access controller's endpoints.
*
* @since 8.6.0
* @param WP_USER $user User object.
* @return bool
*/
abstract protected function user_has_permission( $user ) : bool;
/**
* Check permissions.
*
* Please note that access to this endpoint is also governed by the WC_WCCOM_Site::authenticate_wccom() method.
*
* @since 7.8.0
* @return bool|WP_Error
*/
public function check_permission() {
$current_user = wp_get_current_user();
if ( empty( $current_user ) || ( $current_user instanceof WP_User && ! $current_user->exists() ) ) {
/**
* This filter allows to provide a custom error message when the user is not authenticated.
*
* @since 3.7.0
*/
$error = apply_filters(
WC_WCCOM_Site::AUTH_ERROR_FILTER_NAME,
new Installer_Error( Installer_Error_Codes::NOT_AUTHENTICATED )
);
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
if ( ! $this->user_has_permission( $current_user ) ) {
$error = new Installer_Error( Installer_Error_Codes::NO_PERMISSION );
return new WP_Error(
$error->get_error_code(),
$error->get_error_message(),
array( 'status' => $error->get_http_code() )
);
}
return true;
}
}

View File

@@ -0,0 +1,205 @@
<?php
/**
* WCCOM Site Installer REST API Controller Version
*
* Handles requests to /installer.
*
* @package WooCommerce\WCCom\API
* @since 7.7.0
*/
use WC_REST_WCCOM_Site_Installer_Error as Installer_Error;
defined( 'ABSPATH' ) || exit;
/**
* REST API WCCOM Site Installer Controller Class.
*
* @extends WC_REST_WCCOM_Site_Controller
*/
class WC_REST_WCCOM_Site_Installer_Controller extends WC_REST_WCCOM_Site_Controller {
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'installer';
/**
* Register the routes for plugin auto-installer.
*
* @since 7.7.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'install' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'product-id' => array(
'required' => true,
'type' => 'integer',
),
'run-until-step' => array(
'required' => true,
'type' => 'string',
'enum' => WC_WCCOM_Site_Installation_Manager::STEPS,
),
'idempotency-key' => array(
'required' => true,
'type' => 'string',
),
),
),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/reset',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'reset_install' ),
'permission_callback' => array( $this, 'check_permission' ),
'args' => array(
'product-id' => array(
'required' => true,
'type' => 'integer',
),
'idempotency-key' => array(
'required' => true,
'type' => 'string',
),
),
),
)
);
}
/**
* Check whether user has permission to access controller's endpoints.
*
* @since 8.6.0
* @param WP_USER $user User object.
* @return bool
*/
public function user_has_permission( $user ) : bool {
return user_can( $user, 'install_plugins' ) && user_can( $user, 'install_themes' );
}
/**
* Install WooCommerce.com products.
*
* @since 7.7.0
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function install( $request ) {
try {
$product_id = $request['product-id'];
$run_until_step = $request['run-until-step'];
$idempotency_key = $request['idempotency-key'];
$installation_manager = new WC_WCCOM_Site_Installation_Manager( $product_id, $idempotency_key );
$installation_manager->run_installation( $run_until_step );
$response = $this->success_response( $product_id );
} catch ( Installer_Error $exception ) {
$response = $this->failure_response( $product_id, $exception );
}
return $response;
}
/**
* Reset installation state.
*
* @since 7.7.0
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function reset_install( $request ) {
try {
$product_id = $request['product-id'];
$idempotency_key = $request['idempotency-key'];
$installation_manager = new WC_WCCOM_Site_Installation_Manager( $product_id, $idempotency_key );
$installation_manager->reset_installation();
$response = $this->success_response( $product_id );
} catch ( Installer_Error $exception ) {
$response = $this->failure_response( $product_id, $exception );
}
return $response;
}
/**
* Generate a standardized response for a successful request.
*
* @param int $product_id Product ID.
* @return WP_REST_Response|WP_Error
*/
protected function success_response( $product_id ) {
$state = WC_WCCOM_Site_Installation_State_Storage::get_state( $product_id );
$response = rest_ensure_response(
array(
'success' => true,
'state' => $state ? $this->map_state_to_response( $state ) : null,
)
);
$response->set_status( 200 );
return $response;
}
/**
* Generate a standardized response for a failed request.
*
* @param int $product_id Product ID.
* @param Installer_Error $exception The exception.
* @return WP_REST_Response|WP_Error
*/
protected function failure_response( $product_id, $exception ) {
$state = WC_WCCOM_Site_Installation_State_Storage::get_state( $product_id );
$response = rest_ensure_response(
array(
'success' => false,
'error_code' => $exception->get_error_code(),
'error_message' => $exception->get_error_message(),
'state' => $state ? $this->map_state_to_response( $state ) : null,
)
);
$response->set_status( $exception->get_http_code() );
return $response;
}
/**
* Map the installation state to a response.
*
* @param WC_WCCOM_Site_Installation_State $state The installation state.
* @return array
*/
protected function map_state_to_response( $state ) {
return array(
'product_id' => $state->get_product_id(),
'idempotency_key' => $state->get_idempotency_key(),
'last_step_name' => $state->get_last_step_name(),
'last_step_status' => $state->get_last_step_status(),
'last_step_error' => $state->get_last_step_error(),
'product_type' => $state->get_product_type(),
'product_name' => $state->get_product_name(),
'already_installed_plugin_info' => $state->get_already_installed_plugin_info(),
'started_seconds_ago' => time() - $state->get_started_date(),
);
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* WCCOM Site System Status Report REST API Controller
*
* Handles requests to /ssr.
*
* @package WooCommerce\WCCom\API
* @since 7.8.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API WCCOM System Status Report Controller Class.
*
* @extends WC_REST_WCCOM_Site_Controller
*/
class WC_REST_WCCOM_Site_SSR_Controller extends WC_REST_WCCOM_Site_Controller {
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'ssr';
/**
* Register the routes for SSR Controller.
*
* @since 7.8.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'handle_ssr_request' ),
'permission_callback' => array( $this, 'check_permission' ),
),
),
);
}
/**
* Check whether user has permission to access controller's endpoints.
*
* @since 8.6.0
* @param WP_USER $user User object.
* @return bool
*/
public function user_has_permission( $user ) : bool {
return user_can( $user, 'manage_woocommerce' );
}
/**
* Generate SSR data and submit it to WooCommmerce.com.
*
* @since 7.8.0
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response
*/
public function handle_ssr_request( $request ) {
$ssr_controller = new WC_REST_System_Status_Controller();
$data = $ssr_controller->get_items( $request );
$data = $data->get_data();
// Submit SSR data to WooCommerce.com.
$request = WC_Helper_API::post(
'ssr',
array(
'body' => wp_json_encode( array( 'data' => $data ) ),
'authenticated' => true,
)
);
$response_code = wp_remote_retrieve_response_code( $request );
if ( 201 === $response_code ) {
$response = rest_ensure_response(
array(
'success' => true,
'message' => 'SSR data submitted successfully',
)
);
} else {
$response = rest_ensure_response(
array(
'success' => false,
'error_code' => 'failed_submitting_ssr',
'error_message' => "Submitting SSR data failed with response code: $response_code",
)
);
}
return $response;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* WCCOM Site Status REST API Controller
*
* Handle requests to /status.
*
* @package WooCommerce\WCCom\API
* @since 8.7.0
*/
defined( 'ABSPATH' ) || exit;
/**
* REST API WCCOM Site Status Controller Class.
*
* @extends WC_REST_WCCOM_Site_Status_Controller
*/
class WC_REST_WCCOM_Site_Status_Controller extends WC_REST_WCCOM_Site_Controller {
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'status';
/**
* Register the routes for Site Status Controller.
*
* @since 8.7.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'handle_status_request' ),
'permission_callback' => array( $this, 'check_permission' ),
),
),
);
}
/**
* Check whether user has permission to access controller's endpoints.
*
* @since 8.7.0
* @param WP_USER $user User object.
* @return bool
*/
public function user_has_permission( $user ): bool {
return user_can( $user, 'install_plugins' ) && user_can( $user, 'activate_plugins' );
}
/**
* Get the status details of the site.
*
* @since 8.7.0
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response
*/
public function handle_status_request( $request ) {
return rest_ensure_response(
array(
'success' => true,
'data' => array(
'wc_version' => WC()->version,
'woo_update_manager_installed' => WC_Woo_Update_Manager_Plugin::is_plugin_installed(),
'woo_update_manager_active' => WC_Woo_Update_Manager_Plugin::is_plugin_active(),
),
)
);
}
}