HEX
Server: LiteSpeed
System: Linux eko108.isimtescil.net 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: uyarreklamcomtr (11202)
PHP: 7.4.33
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/wccom-site.tar
class-wc-wccom-site-installer-requirements-check.php000064400000002612151542470570016643 0ustar00<?php
/**
 * WooCommerce.com Product Installation Requirements Check.
 *
 * @package WooCommerce\WCCom
 * @since   3.8.0
 */

use Automattic\Jetpack\Constants;

defined( 'ABSPATH' ) || exit;

/**
 * WC_WCCOM_Site_Installer_Requirements_Check Class
 * Contains functionality to check the necessary requirements for the installer.
 */
class WC_WCCOM_Site_Installer_Requirements_Check {
	/**
	 * Check if the site met the requirements
	 *
	 * @version 3.8.0
	 * @return bool|WP_Error Does the site met the requirements?
	 */
	public static function met_requirements() {
		$errs = array();

		if ( ! self::met_wp_cron_requirement() ) {
			$errs[] = 'wp-cron';
		}

		if ( ! self::met_filesystem_requirement() ) {
			$errs[] = 'filesystem';
		}

		if ( ! empty( $errs ) ) {
			// translators: %s: Requirements unmet.
			return new WP_Error( 'requirements_not_met', sprintf( __( 'Server requirements not met, missing requirement(s): %s.', 'woocommerce' ), implode( ', ', $errs ) ), array( 'status' => 400 ) );
		}

		return true;
	}

	/**
	 * Validates if WP CRON is enabled.
	 *
	 * @since 3.8.0
	 * @return bool
	 */
	private static function met_wp_cron_requirement() {
		return ! Constants::is_true( 'DISABLE_WP_CRON' );
	}

	/**
	 * Validates if `WP_CONTENT_DIR` is writable.
	 *
	 * @since 3.8.0
	 * @return bool
	 */
	private static function met_filesystem_requirement() {
		return is_writable( WP_CONTENT_DIR );
	}
}
class-wc-wccom-site-installer.php000064400000040104151542470570013045 0ustar00<?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 {

	/**
	 * Error message returned install_package if the folder already exists.
	 *
	 * @var string
	 */
	private static $folder_exists = 'folder_exists';

	/**
	 * Default state.
	 *
	 * @var array
	 */
	private static $default_state = array(
		'status'       => 'idle',
		'steps'        => array(),
		'current_step' => null,
	);

	/**
	 * Represents product step state.
	 *
	 * @var array
	 */
	private static $default_step_state = array(
		'download_url'   => '',
		'product_type'   => '',
		'last_step'      => '',
		'last_error'     => '',
		'download_path'  => '',
		'unpacked_path'  => '',
		'installed_path' => '',
		'activate'       => false,
	);

	/**
	 * 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(
		'get_product_info',
		'download_product',
		'unpack_product',
		'move_product',
		'activate_product',
	);

	/**
	 * An instance of the WP_Upgrader class to be used for installation.
	 *
	 * @var \WP_Upgrader $wp_upgrader
	 */
	private static $wp_upgrader;

	/**
	 * Get the product install state.
	 *
	 * @since 3.7.0
	 * @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.
	 *
	 * @since 3.7.0
	 * @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 );
	}

	/**
	 * Reset product install state.
	 *
	 * @since 3.7.0
	 * @param array $products List of product IDs.
	 */
	public static function reset_state( $products = array() ) {
		WC()->queue()->cancel_all( 'woocommerce_wccom_install_products' );
		WC_Helper_Options::update( 'product_install', self::$default_state );
	}

	/**
	 * Schedule installing given list of products.
	 *
	 * @since 3.7.0
	 * @param array $products Array of products where key is product ID and
	 *                        element is install args.
	 * @return array State.
	 */
	public static function schedule_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( array_keys( $products ), self::$default_step_state );
		self::update_state( 'steps', $steps );

		self::update_state( 'current_step', null );

		$args = array(
			'products' => $products,
		);

		// Clear the cache of customer's subscription before asking for them.
		// Thus, they will be re-fetched from WooCommerce.com after a purchase.
		WC_Helper::_flush_subscriptions_cache();

		WC()->queue()->cancel_all( 'woocommerce_wccom_install_products', $args );
		WC()->queue()->add( 'woocommerce_wccom_install_products', $args );

		return self::get_state();
	}

	/**
	 * Install a given product IDs.
	 *
	 * Run via `woocommerce_wccom_install_products` hook.
	 *
	 * @since 3.7.0
	 * @param array $products Array of products where key is product ID and
	 *                        element is install args.
	 */
	public static function install( $products ) {
		$upgrader = self::get_wp_upgrader();

		foreach ( $products as $product_id => $install_args ) {
			self::install_product( $product_id, $install_args, $upgrader );
		}

		self::finish_installation();
	}

	/**
	 * Finish installation by updating the state.
	 *
	 * @since 3.7.0
	 */
	private static function finish_installation() {
		$state = self::get_state();
		if ( empty( $state['steps'] ) ) {
			return;
		}

		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 );
	}

	/**
	 * Install a single product given its ID.
	 *
	 * @since 3.7.0
	 * @param int          $product_id   Product ID.
	 * @param array        $install_args Install args.
	 * @param \WP_Upgrader $upgrader     Core class to handle installation.
	 */
	private static function install_product( $product_id, $install_args, $upgrader ) {
		foreach ( self::$install_steps as $step ) {
			self::do_install_step( $product_id, $install_args, $step, $upgrader );
		}
	}

	/**
	 * Perform product installation step.
	 *
	 * @since 3.7.0
	 * @param int          $product_id   Product ID.
	 * @param array        $install_args Install args.
	 * @param string       $step         Installation step.
	 * @param \WP_Upgrader $upgrader     Core class to handle installation.
	 */
	private static function do_install_step( $product_id, $install_args, $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;

		if ( ! empty( $install_args['activate'] ) ) {
			$state_steps[ $product_id ]['activate'] = true;
		}

		self::update_state(
			'current_step',
			array(
				'product_id' => $product_id,
				'step'       => $step,
			)
		);

		$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();
		} 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'];
					$state_steps[ $product_id ]['product_name'] = $result['product_name'];
					break;
				case 'download_product':
					$state_steps[ $product_id ]['download_path'] = $result;
					break;
				case 'unpack_product':
					$state_steps[ $product_id ]['unpacked_path'] = $result;
					break;
				case 'move_product':
					$state_steps[ $product_id ]['installed_path'] = $result['destination'];
					if ( isset( $result[ self::$folder_exists ] ) ) {
						$state_steps[ $product_id ]['warning'] = array(
							'message'     => self::$folder_exists,
							'plugin_info' => self::get_plugin_info( $state_steps[ $product_id ]['installed_path'] ),
						);
					}
					break;
			}
		}

		self::update_state( 'steps', $state_steps );
	}

	/**
	 * Get product info from its ID.
	 *
	 * @since 3.7.0
	 * @param int $product_id Product ID.
	 * @return array|\WP_Error
	 */
	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'];
		$product_info['product_name'] = $result['name'];

		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.
			return new WP_Error( 'missing_subscription', __( 'Missing product subscription', 'woocommerce' ) );
		} else {
			// 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'] ) ) {
				return new WP_Error( 'missing_product_package', __( 'Could not find product package.', 'woocommerce' ) );
			}

			$product_info['download_url'] = $updates[ $product_id ]['package'];
		}

		return $product_info;
	}

	/**
	 * Download product by its ID and returns the path of the zip package.
	 *
	 * @since 3.7.0
	 * @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 ) {
		$steps = self::get_state( 'steps' );
		if ( empty( $steps[ $product_id ]['download_url'] ) ) {
			return new WP_Error( 'missing_download_url', __( 'Could not find download url for the product.', 'woocommerce' ) );
		}
		return $upgrader->download_package( $steps[ $product_id ]['download_url'] );
	}

	/**
	 * Unpack downloaded product.
	 *
	 * @since 3.7.0
	 * @param int          $product_id Product ID.
	 * @param \WP_Upgrader $upgrader   Core class to handle installation.
	 * @return \WP_Error|string
	 */
	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 find download path.', 'woocommerce' ) );
		}

		return $upgrader->unpack_package( $steps[ $product_id ]['download_path'], true );
	}

	/**
	 * Move product to plugins directory.
	 *
	 * @since 3.7.0
	 * @param int          $product_id Product ID.
	 * @param \WP_Upgrader $upgrader   Core class to handle installation.
	 * @return array|\WP_Error
	 */
	private static function move_product( $product_id, $upgrader ) {
		$steps = self::get_state( 'steps' );
		if ( empty( $steps[ $product_id ]['unpacked_path'] ) ) {
			return new WP_Error( 'missing_unpacked_path', __( 'Could not find unpacked path.', 'woocommerce' ) );
		}

		$destination = 'plugin' === $steps[ $product_id ]['product_type']
			? WP_PLUGIN_DIR
			: get_theme_root();

		$package = array(
			'source'        => $steps[ $product_id ]['unpacked_path'],
			'destination'   => $destination,
			'clear_working' => true,
			'hook_extra'    => array(
				'type'   => $steps[ $product_id ]['product_type'],
				'action' => 'install',
			),
		);

		$result = $upgrader->install_package( $package );

		/**
		 * If install package returns error 'folder_exists' threat as success.
		 */
		if ( is_wp_error( $result ) && array_key_exists( self::$folder_exists, $result->errors ) ) {
			return array(
				self::$folder_exists => true,
				'destination'        => $result->error_data[ self::$folder_exists ],
			);
		}
		return $result;
	}

	/**
	 * Activate product given its product ID.
	 *
	 * @since 3.7.0
	 * @param int $product_id Product ID.
	 * @return \WP_Error|null
	 */
	private static function activate_product( $product_id ) {
		$steps = self::get_state( 'steps' );
		if ( ! $steps[ $product_id ]['activate'] ) {
			return null;
		}

		if ( 'plugin' === $steps[ $product_id ]['product_type'] ) {
			return self::activate_plugin( $product_id );
		}
		return self::activate_theme( $product_id );
	}

	/**
	 * Activate plugin given its product ID.
	 *
	 * @since 3.7.0
	 * @param int $product_id Product ID.
	 * @return \WP_Error|null
	 */
	public static 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 = self::get_wporg_product_dir_name( $product_id );
		if ( false !== $dir_name ) {
			$filename = self::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 ) ) {
			return new WP_Error( 'unknown_filename', __( 'Unknown product filename.', 'woocommerce' ) );
		}

		return activate_plugin( $filename );
	}

	/**
	 * Activate theme given its product ID.
	 *
	 * @since 3.7.0
	 * @param int $product_id Product ID.
	 * @return \WP_Error|null
	 */
	private static 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 = self::get_wporg_product_dir_name( $product_id );
		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 ) ) {
			return new WP_Error( 'unknown_filename', __( 'Unknown product filename.', 'woocommerce' ) );
		}

		return switch_theme( $theme_slug );
	}

	/**
	 * Get installed directory of WP.org product.
	 *
	 * @since 3.7.0
	 * @param int $product_id Product ID.
	 * @return bool|string
	 */
	private static function get_wporg_product_dir_name( $product_id ) {
		$steps   = self::get_state( 'steps' );
		$product = $steps[ $product_id ];

		if ( empty( $product['download_url'] ) || empty( $product['installed_path'] ) ) {
			return false;
		}

		// Check whether product was downloaded from WordPress.org.
		$parsed_url = wp_parse_url( $product['download_url'] );
		if ( ! empty( $parsed_url['host'] ) && 'downloads.wordpress.org' !== $parsed_url['host'] ) {
			return false;
		}

		return basename( $product['installed_path'] );
	}

	/**
	 * 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;
	}
}
class-wc-wccom-site.php000064400000022015151542470570011053 0ustar00<?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';
		require_once WC_ABSPATH . 'includes/wccom-site/class-wc-wccom-site-installer-requirements-check.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/class-wc-rest-wccom-site-installer-controller.php';
		require_once WC_ABSPATH . 'includes/wccom-site/rest-api/endpoints/class-wc-rest-wccom-site-installer-controller-v2.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/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/v1'] = array(
			'installer' => 'WC_REST_WCCOM_Site_Installer_Controller',
			'ssr'       => 'WC_REST_WCCOM_Site_SSR_Controller',
		);

		$namespaces['wccom-site/v2'] = array(
			'installer' => 'WC_REST_WCCOM_Site_Installer_Controller_V2',
		);

		return $namespaces;
	}
}

WC_WCCOM_Site::load();
installation/class-wc-wccom-site-installation-manager.php000064400000013715151542470570017672 0ustar00<?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 );
	}
}
installation/class-wc-wccom-site-installation-state-storage.php000064400000006353151542470600021034 0ustar00<?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 );
	}
}

installation/class-wc-wccom-site-installation-state.php000064400000016112151542470600017364 0ustar00<?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;
	}
}
installation/installation-steps/class-wc-wccom-site-installation-step-activate-product.php000064400000006472151542470610026341 0ustar00<?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.
	 */
	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 ) ) {
			return new Installer_Error( Installer_Error_Codes::UNKNOWN_FILENAME );
		}

		$result = activate_plugin( $filename );

		if ( is_wp_error( $result ) ) {
			return new Installer_Error( Installer_Error_Codes::PLUGIN_ACTIVATION_ERROR, $result->get_error_message() );
		}
	}

	/**
	 * Activate theme.
	 *
	 * @param int $product_id Product ID.
	 */
	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 ) ) {
			return 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() );
	}
}
installation/installation-steps/class-wc-wccom-site-installation-step-download-product.php000064400000002204151542470610026335 0ustar00<?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;
	}
}
installation/installation-steps/class-wc-wccom-site-installation-step-get-product-info.php000064400000006002151542470610026236 0ustar00<?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'] ) ) {
			return new Installer_Error( Installer_Error_Codes::WCCOM_PRODUCT_MISSING_PACKAGE );
		}

		return $updates[ $product_id ]['package'];
	}
}
installation/installation-steps/class-wc-wccom-site-installation-step-move-product.php000064400000003245151542470610025502 0ustar00<?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;
	}
}
installation/installation-steps/class-wc-wccom-site-installation-step-unpack-product.php000064400000002131151542470610026006 0ustar00<?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.
	 */
	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 ) ) {
			return new Installer_Error( Installer_Error_Codes::MISSING_UNPACKED_PATH );
		}

		$this->state->set_unpacked_path( $unpacked_path );

		return $this->state;
	}
}
installation/installation-steps/interface-installaton-step.php000064400000000575151542470610021066 0ustar00<?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();
}
rest-api/class-wc-rest-wccom-site-installer-error-codes.php000064400000011747151542470610017774 0ustar00<?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,
	);
}
rest-api/class-wc-rest-wccom-site-installer-error.php000064400000002217151542470610016671 0ustar00<?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;
	}
}
rest-api/endpoints/class-wc-rest-wccom-site-installer-controller-v2.php000064400000014577151542470610022267 0ustar00<?php
/**
 * WCCOM Site Installer REST API Controller Version 2
 *
 * Handles requests to /installer.
 *
 * @package WooCommerce\WCCom\API
 * @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;

/**
 * REST API WCCOM Site Installer Controller Class.
 *
 * @extends WC_REST_Controller
 */
class WC_REST_WCCOM_Site_Installer_Controller_V2 extends WC_REST_Controller {

	/**
	 * Endpoint namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'wccom-site/v2';

	/**
	 * Route base.
	 *
	 * @var string
	 */
	protected $rest_base = 'installer';

	/**
	 * Register the routes for product reviews.
	 *
	 * @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',
						),
					),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'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 permissions.
	 *
	 * @since 7.7.0
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error
	 */
	public function check_permission( $request ) {
		$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 ( ! user_can( $current_user, 'install_plugins' ) || ! user_can( $current_user, 'install_themes' ) ) {
			$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;
	}

	/**
	 * 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(),
		);
	}
}

rest-api/endpoints/class-wc-rest-wccom-site-installer-controller.php000064400000011606151542470610021730 0ustar00<?php
/**
 * WCCOM Site Installer REST API Controller
 *
 * Handles requests to /installer.
 *
 * @package WooCommerce\WCCom\API
 * @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;

/**
 * REST API WCCOM Site Installer Controller Class.
 *
 * @extends WC_REST_Controller
 */
class WC_REST_WCCOM_Site_Installer_Controller extends WC_REST_Controller {

	/**
	 * Endpoint namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'wccom-site/v1';

	/**
	 * Route base.
	 *
	 * @var string
	 */
	protected $rest_base = 'installer';

	/**
	 * Register the routes for WCCCOM Installer Controller.
	 *
	 * @since 3.7.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_install_state' ),
					'permission_callback' => array( $this, 'check_permission' ),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'install' ),
					'permission_callback' => array( $this, 'check_permission' ),
					'args'                => array(
						'products' => array(
							'required' => true,
							'type'     => 'object',
						),
					),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'reset_install' ),
					'permission_callback' => array( $this, 'check_permission' ),
				),
			)
		);
	}

	/**
	 * Check permissions.
	 *
	 * @since 3.7.0
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error
	 */
	public function check_permission( $request ) {
		$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 ( ! user_can( $current_user, 'install_plugins' ) || ! user_can( $current_user, 'install_themes' ) ) {
			$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;
	}

	/**
	 * Get installation state.
	 *
	 * @since 3.7.0
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error
	 */
	public function get_install_state( $request ) {
		$requirements_met = WC_WCCOM_Site_Installer_Requirements_Check::met_requirements();
		if ( is_wp_error( $requirements_met ) ) {
			return $requirements_met;
		}

		return rest_ensure_response( WC_WCCOM_Site_Installer::get_state() );
	}

	/**
	 * Install WooCommerce.com products.
	 *
	 * @since 3.7.0
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error
	 */
	public function install( $request ) {
		$requirements_met = WC_WCCOM_Site_Installer_Requirements_Check::met_requirements();
		if ( is_wp_error( $requirements_met ) ) {
			return $requirements_met;
		}

		if ( empty( $request['products'] ) ) {
			return new WP_Error( 'missing_products', __( 'Missing products in request body.', 'woocommerce' ), array( 'status' => 400 ) );
		}

		$validation_result = $this->validate_products( $request['products'] );
		if ( is_wp_error( $validation_result ) ) {
			return $validation_result;
		}

		return rest_ensure_response( WC_WCCOM_Site_Installer::schedule_install( $request['products'] ) );
	}

	/**
	 * Reset installation state.
	 *
	 * @since 3.7.0
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error
	 */
	public function reset_install( $request ) {
		$resp = rest_ensure_response( WC_WCCOM_Site_Installer::reset_state() );
		$resp->set_status( 204 );

		return $resp;
	}

	/**
	 * Validate products from request body.
	 *
	 * @since 3.7.0
	 * @param array $products Array of products where key is product ID and
	 *                        element is install args.
	 * @return bool|WP_Error
	 */
	protected function validate_products( $products ) {
		$err = new WP_Error( 'invalid_products', __( 'Invalid products in request body.', 'woocommerce' ), array( 'status' => 400 ) );

		if ( ! is_array( $products ) ) {
			return $err;
		}

		foreach ( $products as $product_id => $install_args ) {
			if ( ! absint( $product_id ) ) {
				return $err;
			}

			if ( empty( $install_args ) || ! is_array( $install_args ) ) {
				return $err;
			}
		}

		return true;
	}
}
rest-api/endpoints/class-wc-rest-wccom-site-ssr-controller.php000064400000006451151542470610020544 0ustar00<?php
/**
 * WCCOM Site System Status Report REST API Controller
 *
 * Handles requests to /ssr.
 *
 * @package WooCommerce\WCCom\API
 * @since   7.8.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 System Status Report Controller Class.
 *
 * @extends WC_REST_Controller
 */
class WC_REST_WCCOM_Site_SSR_Controller extends WC_REST_Controller {

	/**
	 * Endpoint namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'wccom-site/v1';

	/**
	 * 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 permissions.
	 *
	 * Please note that access to this endpoint is also governed by the WC_WCCOM_Site::authenticate_wccom() method.
	 *
	 * @since  7.8.0
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error
	 */
	public function check_permission( $request ) {
		$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 7.8.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 ( ! user_can( $current_user, 'manage_woocommerce' ) ) {
			$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;
	}

	/**
	 * 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;
	}
}