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