diff --git a/plugins/woocommerce/changelog/add-onboarding-theme-rest-api-endpoint-stub b/plugins/woocommerce/changelog/add-onboarding-theme-rest-api-endpoint-stub new file mode 100644 index 00000000000..87ebb6b62c1 --- /dev/null +++ b/plugins/woocommerce/changelog/add-onboarding-theme-rest-api-endpoint-stub @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add recommended Themes REST API Endpoint Stub diff --git a/plugins/woocommerce/src/Admin/API/OnboardingThemes.php b/plugins/woocommerce/src/Admin/API/OnboardingThemes.php index 1d9168e5bb6..10101166fed 100644 --- a/plugins/woocommerce/src/Admin/API/OnboardingThemes.php +++ b/plugins/woocommerce/src/Admin/API/OnboardingThemes.php @@ -61,6 +61,29 @@ class OnboardingThemes extends \WC_REST_Data_Controller { 'schema' => array( $this, 'get_item_schema' ), ) ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/recommended', + array( + 'methods' => 'GET', + 'callback' => array( $this, 'get_recommended_themes' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'industry' => array( + 'type' => 'string', + 'description' => 'Limits the results to themes relevant for this industry (optional)', + ), + 'currency' => array( + 'type' => 'string', + 'enum' => array( 'USD', 'AUD', 'CAD', 'EUR', 'GBP' ), + 'default' => 'USD', + 'description' => 'Returns pricing in this currency (optional, default: USD)', + ), + ), + 'schema' => array( $this, 'get_recommended_item_schema' ), + ) + ); } /** @@ -184,6 +207,141 @@ class OnboardingThemes extends \WC_REST_Data_Controller { ) ); } + /** + * Get recommended themes. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|array Theme activation status. + */ + public function get_recommended_themes( $request ) { + // Check if "industry" and "currency" parameters are provided in the request. + $industry = $request->get_param( 'industry' ); + $currency = $request->get_param( 'currency' ) ?? 'USD'; + + // Return empty response if marketplace suggestions are disabled. + if ( + /** + * Filter allow marketplace suggestions. + * + * User can disable all suggestions via filter. + * + * @since 8.3.0 + */ + ! apply_filters( 'woocommerce_allow_marketplace_suggestions', true ) || + get_option( 'woocommerce_show_marketplace_suggestions', 'yes' ) === 'no' + ) { + + /** + * Filter the onboarding recommended themes response. + * + * @since 8.3.0 + * + * @param array $response The recommended themes response. + * @param array $filtered_themes The filtered themes. + * @param string $industry The industry to filter by (if provided). + * @param string $currency The currency to convert prices to. (USD, AUD, CAD, EUR, GBP). + * + * @return array + */ + return apply_filters( + '__experimental_woocommerce_rest_get_recommended_themes', + array( + 'themes' => array(), + '_links' => array( + 'browse_all' => array( + 'href' => home_url( '/wp-admin/themes.php' ), + ), + ), + ), + $industry, + $currency + ); + } + + // To be implemented: 1. Fetch themes from the marketplace API. 2. Convert prices to the requested currency. + // These are Dotcom themes. + $themes = array( + array( + 'name' => 'Tsubaki', + 'price' => 'Free', + 'color_palettes' => array(), + 'slug' => 'tsubaki', + 'is_active' => false, + 'thumbnail_url' => 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/tsubaki/screenshot.png', + 'link_url' => 'https://wordpress.com/theme/tsubaki/', + ), + array( + 'name' => 'Tazza', + 'price' => 'Free', + 'color_palettes' => array(), + 'slug' => 'tazza', + 'is_active' => false, + 'thumbnail_url' => 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/tazza/screenshot.png', + 'link_url' => 'https://wordpress.com/theme/tazza/', + ), + array( + 'name' => 'Amulet', + 'price' => 'Free', + 'color_palettes' => array(), + 'slug' => 'amulet', + 'is_active' => false, + 'thumbnail_url' => 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/amulet/screenshot.png', + 'link_url' => 'https://wordpress.com/theme/tsubaki/', + ), + array( + 'name' => 'Zaino', + 'price' => 'Free', + 'color_palettes' => array(), + 'slug' => 'zaino', + 'is_active' => false, + 'thumbnail_url' => 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/zaino/screenshot.png', + 'link_url' => 'https://wordpress.com/theme/zaino/', + ), + ); + + // To be implemented: Filter themes based on industry. + if ( $industry ) { + $filtered_themes = array_filter( + $themes, + function ( $theme ) use ( $industry ) { + // Filter themes by industry. + // Example: return $theme['industry'] === $industry;. + return true; + } + ); + } else { + $filtered_themes = $themes; + } + + $response = array( + 'themes' => $filtered_themes, + '_links' => array( + 'browse_all' => array( + 'href' => home_url( '/wp-admin/themes.php' ), + ), + ), + ); + + /** + * Filter the onboarding recommended themes response. + * + * @since 8.3.0 + * + * @param array $response The recommended themes response. + * @param array $filtered_themes The filtered themes. + * @param string $industry The industry to filter by (if provided). + * @param string $currency The currency to convert prices to. (USD, AUD, CAD, EUR, GBP). + * + * @return array + */ + return apply_filters( + '__experimental_woocommerce_rest_get_recommended_themes', + $response, + $industry, + $currency + ); + } + /** * Get the schema, conforming to JSON Schema. * @@ -218,4 +376,94 @@ class OnboardingThemes extends \WC_REST_Data_Controller { return $this->add_additional_fields_schema( $schema ); } + + /** + * Get the recommended themes schema, conforming to JSON Schema. + * + * @return array + */ + public function get_recommended_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'onboarding_theme', + 'type' => 'object', + 'properties' => array( + 'themes' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'type' => 'string', + 'description' => 'Theme Name', + ), + 'price' => array( + 'type' => 'string', + 'description' => 'Price', + ), + 'is_active' => array( + 'type' => 'boolean', + 'description' => 'Whether theme is active', + ), + 'thumbnail_url' => array( + 'type' => 'string', + 'description' => 'Thumbnail URL', + ), + 'link_url' => array( + 'type' => 'string', + 'description' => 'Link URL for the theme', + ), + 'color_palettes' => array( + 'type' => 'array', + 'description' => 'Array of color palette objects', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'primary' => array( + 'type' => 'string', + 'description' => 'Primary color', + ), + 'secondary' => array( + 'type' => 'string', + 'description' => 'Secondary color', + ), + 'background' => array( + 'type' => 'string', + 'description' => 'Background color', + ), + 'primary_border' => array( + 'type' => 'string', + 'description' => 'Primary border color (optional)', + ), + 'secondary_border' => array( + 'type' => 'string', + 'description' => 'Secondary border color (optional)', + ), + ), + ), + ), + ), + ), + ), + '_links' => array( + 'type' => 'object', + 'description' => 'Links related to this response', + 'properties' => array( + 'browse_all' => array( + 'type' => 'object', + 'description' => 'Link to browse all themes', + 'properties' => array( + 'href' => array( + 'type' => 'string', + 'description' => 'URL for browsing all themes', + ), + ), + ), + ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/onboarding-themes.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/onboarding-themes.php index c1647647ad5..aa19d7da4c5 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/onboarding-themes.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/onboarding-themes.php @@ -122,4 +122,223 @@ class WC_Admin_Tests_API_Onboarding_Themes extends WC_REST_Unit_Test_Case { $this->assertEquals( 'woocommerce_rest_invalid_theme', $data['code'] ); } + + /** + * Test that get recommended themes returns the correct data. + */ + public function test_get_recommended_themes() { + wp_set_current_user( $this->user ); + + $request = new WP_REST_Request( 'GET', $this->endpoint . '/recommended' ); + + // Mock the get_option() function to return 'yes' for marketplace suggestions. + // This is done to test the marketplace suggestions scenario. + add_filter( + 'pre_option_woocommerce_show_marketplace_suggestions', + function () { + return 'yes'; + } + ); + + $response = $this->server->dispatch( $request ); + $result = $response->get_data(); + + // Check that the response is an array. + $this->assertIsArray( $result ); + + // Check that the 'themes' key is present in the response. + $this->assertArrayHasKey( 'themes', $result ); + + // Check that the '_links' key is present in the response. + $this->assertArrayHasKey( '_links', $result ); + + // Check that the 'themes' array contains theme data. + $themes = $result['themes']; + $this->assertIsArray( $themes ); + $this->assertGreaterThan( 0, count( $themes ) ); + + // Check that each theme has the expected keys. + foreach ( $themes as $theme ) { + $this->assertArrayHasKey( 'name', $theme ); + $this->assertArrayHasKey( 'price', $theme ); + $this->assertArrayHasKey( 'color_palettes', $theme ); + $this->assertArrayHasKey( 'slug', $theme ); + $this->assertArrayHasKey( 'is_active', $theme ); + $this->assertArrayHasKey( 'thumbnail_url', $theme ); + $this->assertArrayHasKey( 'link_url', $theme ); + } + + // Check that the '_links' array contains 'browse_all'. + $links = $result['_links']; + $this->assertArrayHasKey( 'browse_all', $links ); + + // Check that the 'browse_all' link contains the 'href' key. + $browse_all_link = $links['browse_all']; + $this->assertArrayHasKey( 'href', $browse_all_link ); + } + + /** + * Test that get recommended themes returns the correct data when marketplace suggestions are disabled. + */ + public function test_get_recommended_themes_when_marketplace_suggestions_disabled() { + wp_set_current_user( $this->user ); + + $request = new WP_REST_Request( 'GET', $this->endpoint . '/recommended' ); + + // Mock the get_option() function to return 'no' for marketplace suggestions. + add_filter( + 'pre_option_woocommerce_show_marketplace_suggestions', + function () { + return 'no'; + } + ); + + $response = $this->server->dispatch( $request ); + $result = $response->get_data(); + + // Check that the response is an array. + $this->assertIsArray( $result ); + + // Check that the 'themes' key is present in the response. + $this->assertArrayHasKey( 'themes', $result ); + + // Check that the '_links' key is present in the response. + $this->assertArrayHasKey( '_links', $result ); + + // Check that the 'themes' array is empty. + $themes = $result['themes']; + $this->assertIsArray( $themes ); + $this->assertEquals( 0, count( $themes ) ); + + // Check that the '_links' array contains 'browse_all'. + $links = $result['_links']; + $this->assertArrayHasKey( 'browse_all', $links ); + + // Check that the 'browse_all' link contains the 'href' key. + $browse_all_link = $links['browse_all']; + $this->assertArrayHasKey( 'href', $browse_all_link ); + } + + /** + * Test that get recommended themes returns the correct data with filter applied and marketplace suggestions disabled. + */ + public function test_get_recommended_themes_with_filter_applied_when_marketplace_suggestions_disabled() { + wp_set_current_user( $this->user ); + + $request = new WP_REST_Request( 'GET', $this->endpoint . '/recommended' ); + + $request->set_query_params( + array( + 'industry' => 'example-industry', + 'currency' => 'EUR', + ) + ); + + // Mock the get_option() function to return 'yes' for marketplace suggestions. + // This is done to test the marketplace suggestions scenario. + add_filter( + 'pre_option_woocommerce_show_marketplace_suggestions', + function () { + return 'no'; + } + ); + + // Mock the apply_filters() function to capture the response. + add_filter( + '__experimental_woocommerce_rest_get_recommended_themes', + function ( $result, $industry, $currency ) { + $result['industry'] = $industry; + $result['currency'] = $currency; + $result['themes'] = array(); + return $result; + }, + 10, + 3, + ); + + $response = $this->server->dispatch( $request ); + $result = $response->get_data(); + + $this->assert_filter_applied_results( $result ); + } + + + /** + * Test that get recommended themes returns the correct data with filter applied and marketplace suggestions enabled. + */ + public function test_get_recommended_themes_with_filter_applied_when_marketplace_suggestions_enabled() { + wp_set_current_user( $this->user ); + + $request = new WP_REST_Request( 'GET', $this->endpoint . '/recommended' ); + + $request->set_query_params( + array( + 'industry' => 'example-industry', + 'currency' => 'EUR', + ) + ); + + // Mock the get_option() function to return 'yes' for marketplace suggestions. + // This is done to test the marketplace suggestions scenario. + add_filter( + 'pre_option_woocommerce_show_marketplace_suggestions', + function () { + return 'yes'; + } + ); + + // Mock the apply_filters() function to capture the response. + add_filter( + '__experimental_woocommerce_rest_get_recommended_themes', + function ( $result, $industry, $currency ) { + $result['industry'] = $industry; + $result['currency'] = $currency; + $result['themes'] = array(); + return $result; + }, + 10, + 3, + ); + + $response = $this->server->dispatch( $request ); + $result = $response->get_data(); + + $this->assert_filter_applied_results( $result ); + } + + /** + * Check that get recommended themes returns the correct data with filter applied. + * + * @param array $result The result of API call. + */ + private function assert_filter_applied_results( $result ) { + // Check that the response is an array. + $this->assertIsArray( $result ); + + // Check that the 'themes' key is present in the response. + $this->assertArrayHasKey( 'themes', $result ); + + // Check that the '_links' key is present in the response. + $this->assertArrayHasKey( '_links', $result ); + + // Check that the 'themes' array is empty. + $themes = $result['themes']; + $this->assertIsArray( $themes ); + $this->assertEquals( 0, count( $themes ) ); + + // Check that the 'industry' and 'currency' keys are present in the response. + $this->assertArrayHasKey( 'industry', $result ); + $this->assertEquals( 'example-industry', $result['industry'] ); + + $this->assertArrayHasKey( 'currency', $result ); + $this->assertEquals( 'EUR', $result['currency'] ); + + // Check that the '_links' array contains 'browse_all'. + $links = $result['_links']; + $this->assertArrayHasKey( 'browse_all', $links ); + + // Check that the 'browse_all' link contains the 'href' key. + $browse_all_link = $links['browse_all']; + $this->assertArrayHasKey( 'href', $browse_all_link ); + } }