2019-06-26 03:09:56 +00:00
|
|
|
<?php
|
2019-07-04 11:01:33 +00:00
|
|
|
/**
|
|
|
|
* WooCommerce.com Product Installation.
|
|
|
|
*
|
|
|
|
* @class WC_Helper_Product_Install
|
|
|
|
* @package WooCommerce/Admin
|
|
|
|
*/
|
|
|
|
|
2019-06-26 03:09:56 +00:00
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* WC_Helper_Product_Install Class
|
|
|
|
*
|
|
|
|
* Contains functionalities to install product via helper connection.
|
|
|
|
*/
|
|
|
|
class WC_Helper_Product_Install {
|
|
|
|
/**
|
|
|
|
* Default state.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private static $default_state = array(
|
|
|
|
'status' => 'idle',
|
|
|
|
'steps' => array(),
|
|
|
|
'current_step' => array(),
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents product step state.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private static $default_step_state = array(
|
2019-07-04 08:53:00 +00:00
|
|
|
'download_link' => '',
|
|
|
|
'product_type' => '',
|
2019-06-26 03:09:56 +00:00
|
|
|
'last_step' => '',
|
|
|
|
'last_error' => '',
|
|
|
|
'download_path' => '',
|
|
|
|
'unpacked_path' => '',
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Product install steps. Each step is a method name in this class that
|
|
|
|
* will be passed with product ID arg \WP_Upgrader instance.
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
private static $install_steps = array(
|
2019-07-04 08:53:00 +00:00
|
|
|
'get_product_info',
|
2019-06-26 03:09:56 +00:00
|
|
|
'download_product',
|
|
|
|
'unpack_product',
|
|
|
|
'move_product',
|
|
|
|
'activate_product',
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the product install state.
|
|
|
|
*
|
|
|
|
* @param string $key Key in state data. If empty key is passed array of
|
|
|
|
* state will be returned.
|
|
|
|
*
|
|
|
|
* @return array Product install state.
|
|
|
|
*/
|
|
|
|
public static function get_state( $key = '' ) {
|
|
|
|
$state = WC_Helper_Options::get( 'product_install', self::$default_state );
|
|
|
|
if ( ! empty( $key ) ) {
|
|
|
|
return isset( $state[ $key ] ) ? $state[ $key ] : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $state;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the product install state.
|
|
|
|
*
|
|
|
|
* @param string $key Key in state data.
|
|
|
|
* @param mixed $value Value.
|
|
|
|
*/
|
|
|
|
public static function update_state( $key, $value ) {
|
|
|
|
$state = WC_Helper_Options::get( 'product_install', self::$default_state );
|
|
|
|
|
|
|
|
$state[ $key ] = $value;
|
|
|
|
WC_Helper_Options::update( 'product_install', $state );
|
|
|
|
}
|
|
|
|
|
2019-07-01 01:12:32 +00:00
|
|
|
/**
|
2019-07-08 02:40:28 +00:00
|
|
|
* Reset product install state.
|
2019-07-01 01:12:32 +00:00
|
|
|
*/
|
|
|
|
public static function reset_state() {
|
|
|
|
WC_Helper_Options::update( 'product_install', self::$default_state );
|
|
|
|
}
|
|
|
|
|
2019-06-26 03:09:56 +00:00
|
|
|
/**
|
|
|
|
* Install a given product IDs.
|
|
|
|
*
|
|
|
|
* @param array $products List of product IDs.
|
2019-07-01 09:06:02 +00:00
|
|
|
*
|
|
|
|
* @return array State.
|
2019-06-26 03:09:56 +00:00
|
|
|
*/
|
|
|
|
public static function install( $products ) {
|
|
|
|
$state = self::get_state();
|
|
|
|
$status = ! empty( $state['status'] ) ? $state['status'] : '';
|
|
|
|
if ( 'in-progress' === $status ) {
|
|
|
|
return $state;
|
|
|
|
}
|
|
|
|
|
|
|
|
self::update_state( 'status', 'in-progress' );
|
|
|
|
|
|
|
|
$steps = array_fill_keys( $products, self::$default_step_state );
|
|
|
|
self::update_state( 'steps', $steps );
|
|
|
|
|
2019-07-01 09:06:02 +00:00
|
|
|
// TODO: async install? i.e. queue the job via Action Scheduler.
|
2019-06-26 03:09:56 +00:00
|
|
|
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();
|
|
|
|
$upgrader = new WP_Upgrader( new Automatic_Upgrader_Skin() );
|
2019-07-01 09:26:51 +00:00
|
|
|
$upgrader->init();
|
2019-06-26 03:09:56 +00:00
|
|
|
wp_clean_plugins_cache();
|
|
|
|
|
|
|
|
foreach ( $products as $product_id ) {
|
|
|
|
self::install_product( $product_id, $upgrader );
|
|
|
|
}
|
2019-07-01 09:06:02 +00:00
|
|
|
|
2019-07-01 10:04:26 +00:00
|
|
|
return self::finish_installation();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finish installation by updating the state.
|
|
|
|
*
|
|
|
|
* @return array State.
|
|
|
|
*/
|
|
|
|
private static function finish_installation() {
|
|
|
|
$state = self::get_state();
|
|
|
|
if ( empty( $state['steps'] ) ) {
|
|
|
|
return $state;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ( $state['steps'] as $step ) {
|
|
|
|
if ( ! empty( $step['last_error'] ) ) {
|
|
|
|
$state['status'] = 'has_error';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( 'has_error' !== $state['status'] ) {
|
|
|
|
$state['status'] = 'finished';
|
|
|
|
}
|
|
|
|
|
|
|
|
WC_Helper_Options::update( 'product_install', $state );
|
|
|
|
|
|
|
|
return $state;
|
2019-06-26 03:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Install a single product given its ID.
|
|
|
|
*
|
|
|
|
* @param int $product_id Product ID.
|
|
|
|
* @param \WP_Upgrader $upgrader Core class to handle installation.
|
|
|
|
*/
|
|
|
|
private static function install_product( $product_id, $upgrader ) {
|
2019-07-01 09:06:02 +00:00
|
|
|
foreach ( self::$install_steps as $step ) {
|
2019-06-26 03:09:56 +00:00
|
|
|
self::do_install_step( $product_id, $step, $upgrader );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Perform product installation step.
|
|
|
|
*
|
2019-07-01 09:06:02 +00:00
|
|
|
* @param int $product_id Product ID.
|
|
|
|
* @param string $step Installation step.
|
|
|
|
* @param \WP_Upgrader $upgrader Core class to handle installation.
|
2019-06-26 03:09:56 +00:00
|
|
|
*/
|
|
|
|
private static function do_install_step( $product_id, $step, $upgrader ) {
|
|
|
|
$state_steps = self::get_state( 'steps' );
|
|
|
|
if ( empty( $state_steps[ $product_id ] ) ) {
|
|
|
|
$state_steps[ $product_id ] = self::$default_step_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! empty( $state_steps[ $product_id ]['last_error'] ) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$state_steps[ $product_id ]['last_step'] = $step;
|
|
|
|
|
2019-07-04 11:01:33 +00:00
|
|
|
self::update_state(
|
|
|
|
'current_step',
|
|
|
|
array(
|
|
|
|
'product_id' => $product_id,
|
|
|
|
'step' => $step,
|
|
|
|
)
|
|
|
|
);
|
2019-06-26 03:09:56 +00:00
|
|
|
|
|
|
|
$result = call_user_func( array( __CLASS__, $step ), $product_id, $upgrader );
|
|
|
|
if ( is_wp_error( $result ) ) {
|
|
|
|
$state_steps[ $product_id ]['last_error'] = $result->get_error_message();
|
2019-07-04 08:53:00 +00:00
|
|
|
} else {
|
|
|
|
switch ( $step ) {
|
|
|
|
case 'get_product_info':
|
|
|
|
$state_steps[ $product_id ]['download_url'] = $result['download_url'];
|
|
|
|
$state_steps[ $product_id ]['product_type'] = $result['product_type'];
|
|
|
|
break;
|
|
|
|
case 'download_product':
|
|
|
|
$state_steps[ $product_id ]['download_path'] = $result;
|
|
|
|
break;
|
|
|
|
case 'unpack_product':
|
|
|
|
$state_steps[ $product_id ]['unpacked_path'] = $result;
|
|
|
|
break;
|
|
|
|
}
|
2019-06-26 03:09:56 +00:00
|
|
|
}
|
|
|
|
|
2019-07-01 09:06:02 +00:00
|
|
|
self::update_state( 'steps', $state_steps );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-07-04 08:53:00 +00:00
|
|
|
* Get product info from its ID.
|
2019-07-01 09:06:02 +00:00
|
|
|
*
|
|
|
|
* @param int $product_id Product ID.
|
|
|
|
*
|
2019-07-08 03:25:25 +00:00
|
|
|
* @return bool|\WP_Error
|
2019-07-01 09:06:02 +00:00
|
|
|
*/
|
2019-07-04 08:53:00 +00:00
|
|
|
private static function get_product_info( $product_id ) {
|
|
|
|
$product_info = array(
|
|
|
|
'download_url' => '',
|
|
|
|
'product_type' => '',
|
|
|
|
);
|
|
|
|
|
|
|
|
// Get product info from woocommerce.com.
|
|
|
|
$request = WC_Helper_API::get(
|
|
|
|
add_query_arg(
|
|
|
|
array( 'product_id' => absint( $product_id ) ),
|
|
|
|
'info'
|
|
|
|
),
|
|
|
|
array(
|
|
|
|
'authenticated' => true,
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( 200 !== wp_remote_retrieve_response_code( $request ) ) {
|
|
|
|
return new WP_Error( 'product_info_failed', __( 'Failed to retrieve product info from woocommerce.com', 'woocommerce' ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = json_decode( wp_remote_retrieve_body( $request ), true );
|
|
|
|
|
|
|
|
$product_info['product_type'] = $result['_product_type'];
|
|
|
|
|
|
|
|
if ( ! empty( $result['_wporg_product'] ) && ! empty( $result['download_link'] ) ) {
|
|
|
|
// For wporg product, download is set already from info response.
|
|
|
|
$product_info['download_url'] = $result['download_link'];
|
|
|
|
} elseif ( ! WC_Helper::has_product_subscription( $product_id ) ) {
|
|
|
|
// Non-wporg product needs subscription.
|
2019-07-01 09:06:02 +00:00
|
|
|
return new WP_Error( 'missing_subscription', __( 'Missing product subscription', 'woocommerce' ) );
|
2019-07-04 08:53:00 +00:00
|
|
|
} else {
|
|
|
|
// Retrieve download URL for non-wporg product.
|
|
|
|
$updates = WC_Helper_Updater::get_update_data();
|
|
|
|
if ( empty( $updates[ $product_id ]['package'] ) ) {
|
|
|
|
return new WP_Error( 'missing_product_package', __( 'Could not found product package.', 'woocommerce' ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
$product_info['download_url'] = $updates[ $product_id ]['package'];
|
2019-07-01 09:06:02 +00:00
|
|
|
}
|
|
|
|
|
2019-07-04 08:53:00 +00:00
|
|
|
return $product_info;
|
2019-06-26 03:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Download product by its ID and returns the path of the zip package.
|
|
|
|
*
|
|
|
|
* @param int $product_id Product ID.
|
|
|
|
* @param \WP_Upgrader $upgrader Core class to handle installation.
|
|
|
|
*
|
|
|
|
* @return \WP_Error|string
|
|
|
|
*/
|
|
|
|
private static function download_product( $product_id, $upgrader ) {
|
2019-07-04 08:53:00 +00:00
|
|
|
$steps = self::get_state( 'steps' );
|
|
|
|
if ( empty( $steps[ $product_id ]['download_url'] ) ) {
|
|
|
|
return new WP_Error( 'missing_download_url', __( 'Could not found download url for the product.', 'woocommerce' ) );
|
2019-06-26 03:09:56 +00:00
|
|
|
}
|
2019-07-04 08:53:00 +00:00
|
|
|
return $upgrader->download_package( $steps[ $product_id ]['download_url'] );
|
2019-06-26 03:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-07-01 09:08:45 +00:00
|
|
|
* Unpack downloaded product.
|
2019-06-26 03:09:56 +00:00
|
|
|
*
|
|
|
|
* @param int $product_id Product ID.
|
|
|
|
* @param \WP_Upgrader $upgrader Core class to handle installation.
|
2019-07-08 03:25:25 +00:00
|
|
|
*
|
|
|
|
* @return \WP_Error|string
|
2019-06-26 03:09:56 +00:00
|
|
|
*/
|
|
|
|
private static function unpack_product( $product_id, $upgrader ) {
|
|
|
|
$steps = self::get_state( 'steps' );
|
|
|
|
if ( empty( $steps[ $product_id ]['download_path'] ) ) {
|
|
|
|
return new WP_Error( 'missing_download_path', __( 'Could not found download path.', 'woocommerce' ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
return $upgrader->unpack_package( $steps[ $product_id ]['download_path'], true );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-07-01 09:08:45 +00:00
|
|
|
* Move product to plugins directory.
|
2019-06-26 03:09:56 +00:00
|
|
|
*
|
|
|
|
* @param int $product_id Product ID.
|
|
|
|
* @param \WP_Upgrader $upgrader Core class to handle installation.
|
2019-07-08 03:25:25 +00:00
|
|
|
*
|
|
|
|
* @return array|\WP_Error
|
2019-06-26 03:09:56 +00:00
|
|
|
*/
|
|
|
|
private static function move_product( $product_id, $upgrader ) {
|
|
|
|
$steps = self::get_state( 'steps' );
|
|
|
|
if ( empty( $steps[ $product_id ]['unpacked_path'] ) ) {
|
2019-07-01 09:07:13 +00:00
|
|
|
return new WP_Error( 'missing_unpacked_path', __( 'Could not found unpacked path.', 'woocommerce' ) );
|
2019-06-26 03:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: handle theme.
|
2019-07-04 11:01:33 +00:00
|
|
|
return $upgrader->install_package(
|
|
|
|
array(
|
|
|
|
'source' => $steps[ $product_id ]['unpacked_path'],
|
|
|
|
'destination' => WP_PLUGIN_DIR,
|
|
|
|
'clear_working' => true,
|
|
|
|
'hook_extra' => array(
|
|
|
|
'type' => 'plugin',
|
|
|
|
'action' => 'install',
|
|
|
|
),
|
2019-06-26 03:09:56 +00:00
|
|
|
)
|
2019-07-04 11:01:33 +00:00
|
|
|
);
|
2019-06-26 03:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Activate product given its product ID.
|
|
|
|
*
|
|
|
|
* @param int $product_id Product ID.
|
2019-07-08 03:25:25 +00:00
|
|
|
*
|
|
|
|
* @return \WP_Error|null
|
2019-06-26 03:09:56 +00:00
|
|
|
*/
|
|
|
|
private static function activate_product( $product_id ) {
|
2019-07-01 09:26:51 +00:00
|
|
|
// Clear plugins cache used in `WC_Helper::get_local_woo_plugins`.
|
|
|
|
wp_clean_plugins_cache();
|
|
|
|
|
2019-06-26 03:09:56 +00:00
|
|
|
$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 ) ) {
|
|
|
|
return new WP_Error( 'unknown_filename', __( 'Unknown product filename.', 'woocommerce' ) );
|
|
|
|
}
|
|
|
|
|
2019-07-04 11:01:33 +00:00
|
|
|
// TODO: theme activation support.
|
2019-06-26 03:09:56 +00:00
|
|
|
return activate_plugin( $filename );
|
|
|
|
}
|
|
|
|
}
|