Onboarding: Add theme install and activation endpoints (https://github.com/woocommerce/woocommerce-admin/pull/3482)
* Add API endpoint to install themes * Add API endpoint to activate a theme * Add an array of allowed themes and sanitize slugs * Limit allowed themes to already installed or free themes * Add theme installation tests * Add tests for theme activation * Add onboarding theme installation and activation tests * Remove unused argument in onboarding plugins API
This commit is contained in:
parent
44748bb16e
commit
3da148156e
|
@ -81,6 +81,7 @@ class Init {
|
|||
'Automattic\WooCommerce\Admin\API\OnboardingProfile',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingPlugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingTasks',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingThemes',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -254,10 +254,9 @@ class OnboardingPlugins extends \WC_REST_Data_Controller {
|
|||
/**
|
||||
* Returns a list of active plugins.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return array Active plugins
|
||||
*/
|
||||
public function active_plugins( $request ) {
|
||||
public function active_plugins() {
|
||||
$plugins = Onboarding::get_active_plugins();
|
||||
return( array(
|
||||
'plugins' => array_values( $plugins ),
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Onboarding Themes Controller
|
||||
*
|
||||
* Handles requests to install and activate themes.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\API;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Onboarding Themes Controller.
|
||||
*
|
||||
* @package WooCommerce Admin/API
|
||||
* @extends WC_REST_Data_Controller
|
||||
*/
|
||||
class OnboardingThemes extends \WC_REST_Data_Controller {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc-admin';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'onboarding/themes';
|
||||
|
||||
/**
|
||||
* 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_theme' ),
|
||||
'permission_callback' => array( $this, 'update_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_theme' ),
|
||||
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given request has access to manage themes.
|
||||
*
|
||||
* @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_themes' ) ) {
|
||||
return new \WP_Error( 'woocommerce_rest_cannot_update', __( 'Sorry, you cannot manage themes.', 'woocommerce-admin' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the requested theme.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|array Theme installation status.
|
||||
*/
|
||||
public function install_theme( $request ) {
|
||||
$allowed_themes = Onboarding::get_allowed_themes();
|
||||
$theme = sanitize_title_with_dashes( $request['theme'] );
|
||||
|
||||
if ( ! in_array( $theme, $allowed_themes, true ) ) {
|
||||
return new \WP_Error( 'woocommerce_rest_invalid_theme', __( 'Invalid theme.', 'woocommerce-admin' ), 404 );
|
||||
}
|
||||
|
||||
$slug = sanitize_key( $theme );
|
||||
$installed_themes = wp_get_themes();
|
||||
|
||||
if ( in_array( $slug, array_keys( $installed_themes ), true ) ) {
|
||||
return( array(
|
||||
'slug' => $slug,
|
||||
'name' => $installed_themes[ $slug ]->get( 'Name' ),
|
||||
'status' => 'success',
|
||||
) );
|
||||
}
|
||||
|
||||
include_once ABSPATH . '/wp-admin/includes/admin.php';
|
||||
include_once ABSPATH . '/wp-admin/includes/theme-install.php';
|
||||
include_once ABSPATH . '/wp-admin/includes/theme.php';
|
||||
include_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php';
|
||||
include_once ABSPATH . '/wp-admin/includes/class-theme-upgrader.php';
|
||||
|
||||
$api = themes_api(
|
||||
'theme_information',
|
||||
array(
|
||||
'slug' => $slug,
|
||||
'fields' => array(
|
||||
'sections' => false,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $api ) ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_rest_theme_install',
|
||||
sprintf(
|
||||
/* translators: %s: theme slug (example: woocommerce-services) */
|
||||
__( 'The requested theme `%s` could not be installed. Theme API call failed.', 'woocommerce-admin' ),
|
||||
$slug
|
||||
),
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
$upgrader = new \Theme_Upgrader( new \Automatic_Upgrader_Skin() );
|
||||
$result = $upgrader->install( $api->download_link );
|
||||
|
||||
if ( is_wp_error( $result ) || is_null( $result ) ) {
|
||||
return new \WP_Error(
|
||||
'woocommerce_rest_theme_install',
|
||||
sprintf(
|
||||
/* translators: %s: theme slug (example: woocommerce-services) */
|
||||
__( 'The requested theme `%s` could not be installed.', 'woocommerce-admin' ),
|
||||
$slug
|
||||
),
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'slug' => $slug,
|
||||
'name' => $api->name,
|
||||
'status' => 'success',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the requested theme.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|array Theme activation status.
|
||||
*/
|
||||
public function activate_theme( $request ) {
|
||||
$allowed_themes = Onboarding::get_allowed_themes();
|
||||
$theme = sanitize_title_with_dashes( $request['theme'] );
|
||||
if ( ! in_array( $theme, $allowed_themes, true ) ) {
|
||||
return new \WP_Error( 'woocommerce_rest_invalid_theme', __( 'Invalid theme.', 'woocommerce-admin' ), 404 );
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/theme.php';
|
||||
|
||||
$installed_themes = wp_get_themes();
|
||||
|
||||
if ( ! in_array( $theme, array_keys( $installed_themes ), true ) ) {
|
||||
/* translators: %s: theme slug (example: woocommerce-services) */
|
||||
return new \WP_Error( 'woocommerce_rest_invalid_theme', sprintf( __( 'Invalid theme %s.', 'woocommerce-admin' ), $slug ), 404 );
|
||||
}
|
||||
|
||||
$result = switch_theme( $theme );
|
||||
if ( ! is_null( $result ) ) {
|
||||
return new \WP_Error( 'woocommerce_rest_invalid_theme', sprintf( __( 'The requested theme could not be activated.', 'woocommerce-admin' ), $slug ), 500 );
|
||||
}
|
||||
|
||||
return( array(
|
||||
'slug' => $theme,
|
||||
'status' => 'success',
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => 'onboarding_theme',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'slug' => array(
|
||||
'description' => __( 'Theme slug.', 'woocommerce-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Theme name.', 'woocommerce-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'status' => array(
|
||||
'description' => __( 'Theme status.', 'woocommerce-admin' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
}
|
|
@ -196,10 +196,11 @@ class Onboarding {
|
|||
);
|
||||
|
||||
foreach ( $theme_data->products as $theme ) {
|
||||
$slug = sanitize_title( $theme->slug );
|
||||
$slug = sanitize_title_with_dashes( $theme->slug );
|
||||
$themes[ $slug ] = (array) $theme;
|
||||
$themes[ $slug ]['is_installed'] = false;
|
||||
$themes[ $slug ]['has_woocommerce_support'] = true;
|
||||
$themes[ $slug ]['slug'] = $slug;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,6 +498,7 @@ class Onboarding {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of active plugins, relevent to the onboarding wizard.
|
||||
*
|
||||
|
@ -515,6 +517,26 @@ class Onboarding {
|
|||
return $active_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of themes that can be installed & activated via the onboarding wizard.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_allowed_themes() {
|
||||
$allowed_themes = array();
|
||||
$themes = self::get_themes();
|
||||
|
||||
foreach ( $themes as $theme ) {
|
||||
$price = preg_replace( '/&#?[a-z0-9]+;/i', '', $theme['price'] );
|
||||
|
||||
if ( $theme['is_installed'] || '0.00' === $price ) {
|
||||
$allowed_themes[] = $theme['slug'];
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_admin_onboarding_themes_whitelist', $allowed_themes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the app know that we will be showing the onboarding route, so wp-admin elements should be hidden while loading.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
/**
|
||||
* Onboarding Plugins REST API Test
|
||||
*
|
||||
* @package WooCommerce Admin\Tests\API
|
||||
*/
|
||||
|
||||
use \Automattic\WooCommerce\Admin\API\OnboardingPlugins;
|
||||
use \Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
|
||||
/**
|
||||
* WC Tests API Onboarding Plugins
|
||||
*/
|
||||
class WC_Tests_API_Onboarding_Plugins extends WC_REST_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Endpoints.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = '/wc-admin/onboarding/plugins';
|
||||
|
||||
/**
|
||||
* Setup test data. Called before every test.
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->user = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that installation without permission is unauthorized.
|
||||
*/
|
||||
public function test_install_without_permission() {
|
||||
$response = $this->server->dispatch( new WP_REST_Request( 'POST', $this->endpoint . '/install' ) );
|
||||
$this->assertEquals( 401, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that installing a valid plugin works.
|
||||
*/
|
||||
public function test_install_plugin() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/install' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'plugin' => 'facebook-for-woocommerce',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
$plugins = get_plugins();
|
||||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertEquals( 'facebook-for-woocommerce', $data['slug'] );
|
||||
$this->assertEquals( 'success', $data['status'] );
|
||||
$this->assertArrayHasKey( 'facebook-for-woocommerce/facebook-for-woocommerce.php', $plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that installing an invalid plugin fails.
|
||||
*/
|
||||
public function test_install_invalid_plugin() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/install' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'plugin' => 'invalid-plugin-name',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 'woocommerce_rest_invalid_plugin', $data['code'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that activating a valid plugin works.
|
||||
*/
|
||||
public function test_activate_plugin() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/activate' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'plugins' => 'facebook-for-woocommerce',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
$active_plugins = Onboarding::get_active_plugins();
|
||||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertContains( 'facebook-for-woocommerce', $data['activatedPlugins'] );
|
||||
$this->assertEquals( 'success', $data['status'] );
|
||||
$this->assertContains( 'facebook-for-woocommerce', $active_plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that activating an invalid plugin fails.
|
||||
*/
|
||||
public function test_activate_invalid_plugin() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/activate' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'plugins' => 'invalid-plugin-name',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 'woocommerce_rest_invalid_plugins', $data['code'] );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
/**
|
||||
* Onboarding Themes REST API Test
|
||||
*
|
||||
* @package WooCommerce Admin\Tests\API
|
||||
*/
|
||||
|
||||
use \Automattic\WooCommerce\Admin\API\OnboardingThemes;
|
||||
use \Automattic\WooCommerce\Admin\Features\Onboarding;
|
||||
|
||||
/**
|
||||
* WC Tests API Onboarding Themes
|
||||
*/
|
||||
class WC_Tests_API_Onboarding_Themes extends WC_REST_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Endpoints.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endpoint = '/wc-admin/onboarding/themes';
|
||||
|
||||
/**
|
||||
* Setup test data. Called before every test.
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->user = $this->factory->user->create(
|
||||
array(
|
||||
'role' => 'administrator',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that installation without permission is unauthorized.
|
||||
*/
|
||||
public function test_install_without_permission() {
|
||||
$response = $this->server->dispatch( new WP_REST_Request( 'POST', $this->endpoint . '/install' ) );
|
||||
$this->assertEquals( 401, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that installing a valid theme works.
|
||||
*/
|
||||
public function test_install_theme() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/install' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'theme' => 'storefront',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
$themes = wp_get_themes();
|
||||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertEquals( 'storefront', $data['slug'] );
|
||||
$this->assertEquals( 'success', $data['status'] );
|
||||
$this->assertArrayHasKey( 'storefront', $themes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that installing an invalid theme fails.
|
||||
*/
|
||||
public function test_install_invalid_theme() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/install' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'theme' => 'invalid-theme-name',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 'woocommerce_rest_invalid_theme', $data['code'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that activating a valid theme works.
|
||||
*/
|
||||
public function test_activate_theme() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$previous_theme = get_stylesheet();
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/activate' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'theme' => 'storefront',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
$active_theme = get_stylesheet();
|
||||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertEquals( 'storefront', $data['slug'] );
|
||||
$this->assertEquals( 'success', $data['status'] );
|
||||
$this->assertNotEquals( 'storefront', $previous_theme );
|
||||
$this->assertEquals( 'storefront', $active_theme );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that activating an invalid theme fails.
|
||||
*/
|
||||
public function test_activate_invalid_theme() {
|
||||
wp_set_current_user( $this->user );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', $this->endpoint . '/activate' );
|
||||
$request->set_query_params(
|
||||
array(
|
||||
'theme' => 'invalid-theme-name',
|
||||
)
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 'woocommerce_rest_invalid_theme', $data['code'] );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue