<?php
/**
 * REST API Plugins Controller
 *
 * Handles requests to install and activate depedent plugins.
 */

namespace Automattic\Kkart\Admin\API;

use Automattic\Kkart\Admin\Features\Onboarding;
use Automattic\Kkart\Admin\PluginsHelper;
use \Automattic\Kkart\Admin\Notes\InstallJPAndKKARTSPlugins;

defined( 'ABSPATH' ) || exit;

/**
 * Plugins Controller.
 *
 * @extends KKART_REST_Data_Controller
 */
class Plugins extends \KKART_REST_Data_Controller {
	/**
	 * Endpoint namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'kkart-admin';

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

	/**
	 * Register routes.
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/install',
			array(
				array(
					'methods'             => \WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'install_plugins' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/active',
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'active_plugins' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/installed',
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'installed_plugins' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/activate',
			array(
				array(
					'methods'             => \WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'activate_plugins' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/connect-jetpack',
			array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'connect_jetpack' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_connect_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/request-wccom-connect',
			array(
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'request_wccom_connect' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_connect_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/finish-wccom-connect',
			array(
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'finish_wccom_connect' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_connect_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/connect-paypal',
			array(
				array(
					'methods'             => \WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'connect_paypal' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_connect_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/connect-wcpay',
			array(
				array(
					'methods'             => \WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'connect_wcpay' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_connect_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/connect-square',
			array(
				array(
					'methods'             => \WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'connect_square' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_connect_schema' ),
			)
		);
	}

	/**
	 * Check if a given request has access to manage plugins.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|boolean
	 */
	public function update_item_permissions_check( $request ) {
		if ( ! current_user_can( 'install_plugins' ) ) {
			return new \WP_Error( 'kkart_rest_cannot_update', __( 'Sorry, you cannot manage plugins.', 'kkart' ), array( 'status' => rest_authorization_required_code() ) );
		}
		return true;
	}

	/**
	 * Create an alert notification in response to an error installing a plugin.
	 *
	 * @todo This should be moved to a filter to make this API more generic and less plugin-specific.
	 *
	 * @param string $slug The slug of the plugin being installed.
	 */
	private function create_install_plugin_error_inbox_notification_for_jetpack_installs( $slug ) {
		// Exit early if we're not installing the Jetpack or the Kkart Shipping & Tax plugins.
		if ( 'jetpack' !== $slug && 'kkart-services' !== $slug ) {
			return;
		}

		InstallJPAndKKARTSPlugins::possibly_add_note();
	}

	/**
	 * Install the requested plugin.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|array Plugin Status
	 */
	public function install_plugin( $request ) {
		kkart_deprecated_function( 'install_plugin', '4.3', '\Automattic\Kkart\Admin\API\Plugins()->install_plugins' );
		// This method expects a `plugin` argument to be sent, install plugins requires plugins.
		$request['plugins'] = $request['plugin'];
		return self::install_plugins( $request );
	}

	/**
	 * Installs the requested plugins.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|array Plugin Status
	 */
	public function install_plugins( $request ) {
		$allowed_plugins = self::get_allowed_plugins();
		$plugins         = explode( ',', $request['plugins'] );

		if ( empty( $request['plugins'] ) || ! is_array( $plugins ) ) {
			return new \WP_Error( 'kkart_rest_invalid_plugins', __( 'Plugins must be a non-empty array.', 'kkart' ), 404 );
		}

		require_once KKART_ADMIN_DIR . 'includes/plugin.php';
		include_once KKART_ADMIN_DIR . 'includes/admin.php';
		include_once KKART_ADMIN_DIR . 'includes/plugin-install.php';
		include_once KKART_ADMIN_DIR . 'includes/plugin.php';
		include_once KKART_ADMIN_DIR . 'includes/class-wp-upgrader.php';
		include_once KKART_ADMIN_DIR . 'includes/class-plugin-upgrader.php';

		$existing_plugins  = get_plugins();
		$installed_plugins = array();
		$results           = array();
		$errors            = new \WP_Error();

		foreach ( $plugins as $plugin ) {
			$slug = sanitize_key( $plugin );
			$path = isset( $allowed_plugins[ $slug ] ) ? $allowed_plugins[ $slug ] : false;

			if ( ! $path ) {
				$errors->add(
					$plugin,
					/* translators: %s: plugin slug (example: kkart-services) */
					sprintf( __( 'The requested plugin `%s` is not in the list of allowed plugins.', 'kkart' ), $slug )
				);
				continue;
			}

			if ( in_array( $path, array_keys( $existing_plugins ), true ) ) {
				$installed_plugins[] = $plugin;
				continue;
			}

			$api = plugins_api(
				'plugin_information',
				array(
					'slug'   => $slug,
					'fields' => array(
						'sections' => false,
					),
				)
			);

			if ( is_wp_error( $api ) ) {
				$properties = array(
					/* translators: %s: plugin slug (example: kkart-services) */
					'error_message' => __( 'The requested plugin `%s` could not be installed. Plugin API call failed.', 'kkart' ),
					'api'           => $api,
					'slug'          => $slug,
				);
				kkart_admin_record_tracks_event( 'install_plugin_error', $properties );

				$this->create_install_plugin_error_inbox_notification_for_jetpack_installs( $slug );

				$errors->add(
					$plugin,
					sprintf(
						/* translators: %s: plugin slug (example: kkart-services) */
						__( 'The requested plugin `%s` could not be installed. Plugin API call failed.', 'kkart' ),
						$slug
					)
				);

				continue;
			}

			$upgrader           = new \Plugin_Upgrader( new \Automatic_Upgrader_Skin() );
			$result             = $upgrader->install( $api->download_link );
			$results[ $plugin ] = $result;

			if ( is_wp_error( $result ) || is_null( $result ) ) {
				$properties = array(
					/* translators: %s: plugin slug (example: kkart-services) */
					'error_message' => __( 'The requested plugin `%s` could not be installed.', 'kkart' ),
					'slug'          => $slug,
					'api'           => $api,
					'upgrader'      => $upgrader,
					'result'        => $result,
				);
				kkart_admin_record_tracks_event( 'install_plugin_error', $properties );

				$this->create_install_plugin_error_inbox_notification_for_jetpack_installs( $slug );

				$errors->add(
					$plugin,
					sprintf(
						/* translators: %s: plugin slug (example: kkart-services) */
						__( 'The requested plugin `%s` could not be installed.  Upgrader install failed.', 'kkart' ),
						$slug
					)
				);
				continue;
			}

			$installed_plugins[] = $plugin;
		}

		return array(
			'data'    => array(
				'installed' => $installed_plugins,
				'results'   => $results,
			),
			'errors'  => $errors,
			'success' => count( $errors->errors ) === 0,
			'message' => count( $errors->errors ) === 0
				? __( 'Plugins were successfully installed.', 'kkart' )
				: __( 'There was a problem installing some of the requested plugins.', 'kkart' ),
		);
	}

	/**
	 * Gets an array of plugins that can be installed & activated.
	 *
	 * @return array
	 */
	public static function get_allowed_plugins() {
		return apply_filters( 'kkart_admin_plugins_whitelist', array() );
	}

	/**
	 * Returns a list of active plugins in API format.
	 *
	 * @return array Active plugins
	 */
	public static function active_plugins() {
		$allowed = self::get_allowed_plugins();
		$plugins = array_values( array_intersect( PluginsHelper::get_active_plugin_slugs(), array_keys( $allowed ) ) );
		return( array(
			'plugins' => array_values( $plugins ),
		) );
	}
	/**
	 * Returns a list of active plugins.
	 *
	 * @return array Active plugins
	 */
	public static function get_active_plugins() {
		$data = self::active_plugins();
		return $data['plugins'];
	}

	/**
	 * Returns a list of installed plugins.
	 *
	 * @return array Installed plugins
	 */
	public function installed_plugins() {
		return( array(
			'plugins' => PluginsHelper::get_installed_plugin_slugs(),
		) );
	}

	/**
	 * Activate the requested plugin.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|array Plugin Status
	 */
	public function activate_plugins( $request ) {
		$allowed_plugins   = self::get_allowed_plugins();
		$plugins           = explode( ',', $request['plugins'] );
		$errors            = new \WP_Error();
		$activated_plugins = array();

		if ( empty( $request['plugins'] ) || ! is_array( $plugins ) ) {
			return new \WP_Error( 'kkart_rest_invalid_plugins', __( 'Plugins must be a non-empty array.', 'kkart' ), 404 );
		}

		require_once KKART_ADMIN_DIR . 'includes/plugin.php';

		foreach ( $plugins as $plugin ) {
			$slug = $plugin;
			$path = isset( $allowed_plugins[ $slug ] ) ? $allowed_plugins[ $slug ] : false;

			if ( ! $path ) {
				$errors->add(
					$plugin,
					/* translators: %s: plugin slug (example: kkart-services) */
					sprintf( __( 'The requested plugin `%s`. is not in the list of allowed plugins.', 'kkart' ), $slug )
				);
				continue;
			}

			if ( ! PluginsHelper::is_plugin_installed( $path ) ) {
				$errors->add(
					$plugin,
					/* translators: %s: plugin slug (example: kkart-services) */
					sprintf( __( 'The requested plugin `%s`. is not yet installed.', 'kkart' ), $slug )
				);
				continue;
			}

			$result = activate_plugin( $path );
			if ( ! is_null( $result ) ) {
				$this->create_install_plugin_error_inbox_notification_for_jetpack_installs( $slug );

				$errors->add(
					$plugin,
					/* translators: %s: plugin slug (example: kkart-services) */
					sprintf( __( 'The requested plugin `%s` could not be activated.', 'kkart' ), $slug )
				);
				continue;
			}

			$activated_plugins[] = $plugin;
		}

		return( array(
			'data'    => array(
				'activated' => $activated_plugins,
				'active'    => self::get_active_plugins(),
			),
			'errors'  => $errors,
			'success' => count( $errors->errors ) === 0,
			'message' => count( $errors->errors ) === 0
				? __( 'Plugins were successfully activated.', 'kkart' )
				: __( 'There was a problem activating some of the requested plugins.', 'kkart' ),
		) );
	}

	/**
	 * Generates a Jetpack Connect URL.
	 *
	 * @param  WP_REST_Request $request Full details about the request.
	 * @return WP_Error|array Connection URL for Jetpack
	 */
	public function connect_jetpack( $request ) {
		if ( ! class_exists( '\Jetpack' ) ) {
			return new \WP_Error( 'kkart_rest_jetpack_not_active', __( 'Jetpack is not installed or active.', 'kkart' ), 404 );
		}

		$redirect_url = apply_filters( 'kkart_admin_onboarding_jetpack_connect_redirect_url', esc_url_raw( $request['redirect_url'] ) );
		$connect_url  = \Jetpack::init()->build_connect_url( true, $redirect_url, 'kkart-onboarding' );

		$calypso_env = defined( 'KKART_CALYPSO_ENVIRONMENT' ) && in_array( KKART_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ), true ) ? KKART_CALYPSO_ENVIRONMENT : 'production';
		$connect_url = add_query_arg( array( 'calypso_env' => $calypso_env ), $connect_url );

		return( array(
			'slug'          => 'jetpack',
			'name'          => __( 'Jetpack', 'kkart' ),
			'connectAction' => $connect_url,
		) );
	}

	/**
	 *  Kicks off the KKARTCOM Connect process.
	 *
	 * @return WP_Error|array Connection URL for Kkart.com
	 */
	public function request_wccom_connect() {
		include_once KKART_ABSPATH . 'includes/admin/helper/class-kkart-helper-api.php';
		if ( ! class_exists( 'KKART_Helper_API' ) ) {
			return new \WP_Error( 'kkart_rest_helper_not_active', __( 'There was an error loading the Kkart.com Helper API.', 'kkart' ), 404 );
		}

		$redirect_uri = kkart_admin_url( '&task=connect&wccom-connected=1' );

		$request = \KKART_Helper_API::post(
			'oauth/request_token',
			array(
				'body' => array(
					'home_url'     => home_url(),
					'redirect_uri' => $redirect_uri,
				),
			)
		);

		$code = wp_remote_retrieve_response_code( $request );
		if ( 200 !== $code ) {
			return new \WP_Error( 'kkart_rest_helper_connect', __( 'There was an error connecting to Kkart.com. Please try again.', 'kkart' ), 500 );
		}

		$secret = json_decode( wp_remote_retrieve_body( $request ) );
		if ( empty( $secret ) ) {
			return new \WP_Error( 'kkart_rest_helper_connect', __( 'There was an error connecting to Kkart.com. Please try again.', 'kkart' ), 500 );
		}

		do_action( 'kkart_helper_connect_start' );

		$connect_url = add_query_arg(
			array(
				'home_url'     => rawurlencode( home_url() ),
				'redirect_uri' => rawurlencode( $redirect_uri ),
				'secret'       => rawurlencode( $secret ),
				'wccom-from'   => 'onboarding',
			),
			\KKART_Helper_API::url( 'oauth/authorize' )
		);

		if ( defined( 'KKART_CALYPSO_ENVIRONMENT' ) && in_array( KKART_CALYPSO_ENVIRONMENT, array( 'development', 'wpcalypso', 'horizon', 'stage' ), true ) ) {
			$connect_url = add_query_arg(
				array(
					'calypso_env' => KKART_CALYPSO_ENVIRONMENT,
				),
				$connect_url
			);
		}

		return( array(
			'connectAction' => $connect_url,
		) );
	}

	/**
	 * Finishes connecting to Kkart.com.
	 *
	 * @param  object $rest_request Request details.
	 * @return WP_Error|array Contains success status.
	 */
	public function finish_wccom_connect( $rest_request ) {
		include_once KKART_ABSPATH . 'includes/admin/helper/class-kkart-helper.php';
		include_once KKART_ABSPATH . 'includes/admin/helper/class-kkart-helper-api.php';
		include_once KKART_ABSPATH . 'includes/admin/helper/class-kkart-helper-updater.php';
		include_once KKART_ABSPATH . 'includes/admin/helper/class-kkart-helper-options.php';
		if ( ! class_exists( 'KKART_Helper_API' ) ) {
			return new \WP_Error( 'kkart_rest_helper_not_active', __( 'There was an error loading the Kkart.com Helper API.', 'kkart' ), 404 );
		}

		// Obtain an access token.
		$request = \KKART_Helper_API::post(
			'oauth/access_token',
			array(
				'body' => array(
					'request_token' => wp_unslash( $rest_request['request_token'] ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
					'home_url'      => home_url(),
				),
			)
		);

		$code = wp_remote_retrieve_response_code( $request );
		if ( 200 !== $code ) {
			return new \WP_Error( 'kkart_rest_helper_connect', __( 'There was an error connecting to Kkart.com. Please try again.', 'kkart' ), 500 );
		}

		$access_token = json_decode( wp_remote_retrieve_body( $request ), true );
		if ( ! $access_token ) {
			return new \WP_Error( 'kkart_rest_helper_connect', __( 'There was an error connecting to Kkart.com. Please try again.', 'kkart' ), 500 );
		}

		\KKART_Helper_Options::update(
			'auth',
			array(
				'access_token'        => $access_token['access_token'],
				'access_token_secret' => $access_token['access_token_secret'],
				'site_id'             => $access_token['site_id'],
				'user_id'             => get_current_user_id(),
				'updated'             => time(),
			)
		);

		if ( ! \KKART_Helper::_flush_authentication_cache() ) {
			\KKART_Helper_Options::update( 'auth', array() );
			return new \WP_Error( 'kkart_rest_helper_connect', __( 'There was an error connecting to Kkart.com. Please try again.', 'kkart' ), 500 );
		}

		delete_transient( '_kkart_helper_subscriptions' );
		\KKART_Helper_Updater::flush_updates_cache();

		do_action( 'kkart_helper_connected' );

		return array(
			'success' => true,
		);
	}

	/**
	 * Returns a URL that can be used to connect to PayPal.
	 *
	 * @return WP_Error|array Connect URL.
	 */
	public function connect_paypal() {
		if ( ! function_exists( 'kkart_gateway_ppec' ) ) {
			return new \WP_Error( 'kkart_rest_helper_connect', __( 'There was an error connecting to PayPal.', 'kkart' ), 500 );
		}

		$redirect_url = add_query_arg(
			array(
				'env'                     => 'live',
				'kkart_ppec_ips_admin_nonce' => wp_create_nonce( 'kkart_ppec_ips' ),
			),
			kkart_admin_url( '&task=payments&method=paypal&paypal-connect-finish=1' )
		);

		// https://github.com/kkart/kkart-gateway-paypal-express-checkout/blob/b6df13ba035038aac5024d501e8099a37e13d6cf/includes/class-kkart-gateway-ppec-ips-handler.php#L79-L93.
		$query_args  = array(
			'redirect'    => rawurlencode( $redirect_url ),
			'countryCode' => KKART()->countries->get_base_country(),
			'merchantId'  => md5( site_url( '/' ) . time() ),
		);
		$connect_url = add_query_arg( $query_args, kkart_gateway_ppec()->ips->get_middleware_login_url( 'live' ) );

		return( array(
			'connectUrl' => $connect_url,
		) );
	}

	/**
	 * Returns a URL that can be used to connect to Square.
	 *
	 * @return WP_Error|array Connect URL.
	 */
	public function connect_square() {
		if ( ! class_exists( '\Kkart\Square\Handlers\Connection' ) ) {
			return new \WP_Error( 'kkart_rest_helper_connect', __( 'There was an error connecting to Square.', 'kkart' ), 500 );
		}

		if ( 'US' === KKART()->countries->get_base_country() ) {
			$profile = get_option( Onboarding::PROFILE_DATA_OPTION, array() );
			if ( ! empty( $profile['industry'] ) ) {
				$has_cbd_industry = in_array( 'cbd-other-hemp-derived-products', array_column( $profile['industry'], 'slug' ), true );
			}
		}

		if ( $has_cbd_industry ) {
			$url = 'https://squareup.com/t/f_partnerships/d_referrals/p_kkart/c_general/o_none/l_us/dt_alldevice/pr_payments/?route=/solutions/cbd';
		} else {
			$url = \Kkart\Square\Handlers\Connection::CONNECT_URL_PRODUCTION;
		}

		$redirect_url = wp_nonce_url( kkart_admin_url( '&task=payments&method=square&square-connect-finish=1' ), 'kkart_square_connected' );
		$args         = array(
			'redirect' => rawurlencode( rawurlencode( $redirect_url ) ),
			'scopes'   => implode(
				',',
				array(
					'MERCHANT_PROFILE_READ',
					'PAYMENTS_READ',
					'PAYMENTS_WRITE',
					'ORDERS_READ',
					'ORDERS_WRITE',
					'CUSTOMERS_READ',
					'CUSTOMERS_WRITE',
					'SETTLEMENTS_READ',
					'ITEMS_READ',
					'ITEMS_WRITE',
					'INVENTORY_READ',
					'INVENTORY_WRITE',
				)
			),
		);

		$connect_url = add_query_arg( $args, $url );

		return( array(
			'connectUrl' => $connect_url,
		) );
	}

	/**
	 * Returns a URL that can be used to by KKARTPay to verify business details with Stripe.
	 *
	 * @return WP_Error|array Connect URL.
	 */
	public function connect_wcpay() {
		if ( ! class_exists( 'KKART_Payments_Account' ) ) {
			return new \WP_Error( 'kkart_rest_helper_connect', __( 'There was an error communicating with the Kkart Payments plugin.', 'kkart' ), 500 );
		}

		$connect_url = add_query_arg(
			array(
				'wcpay-connect' => 'KKARTADMIN_PAYMENT_TASK',
				'_wpnonce'      => wp_create_nonce( 'wcpay-connect' ),
			),
			admin_url()
		);

		return( array(
			'connectUrl' => $connect_url,
		) );
	}

	/**
	 * Get the schema, conforming to JSON Schema.
	 *
	 * @return array
	 */
	public function get_item_schema() {
		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'plugins',
			'type'       => 'object',
			'properties' => array(
				'slug'   => array(
					'description' => __( 'Plugin slug.', 'kkart' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'name'   => array(
					'description' => __( 'Plugin name.', 'kkart' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'status' => array(
					'description' => __( 'Plugin status.', 'kkart' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
			),
		);

		return $this->add_additional_fields_schema( $schema );
	}

	/**
	 * Get the schema, conforming to JSON Schema.
	 *
	 * @return array
	 */
	public function get_connect_schema() {
		$schema = $this->get_item_schema();
		unset( $schema['properties']['status'] );
		$schema['properties']['connectAction'] = array(
			'description' => __( 'Action that should be completed to connect Jetpack.', 'kkart' ),
			'type'        => 'string',
			'context'     => array( 'view', 'edit' ),
			'readonly'    => true,
		);
		return $schema;
	}
}
