Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.00% covered (warning)
75.00%
36 / 48
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCom_Themes_Api
75.00% covered (warning)
75.00%
36 / 48
62.50% covered (warning)
62.50%
5 / 8
26.25
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetch_themes
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 handle_request
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 fetch_all_non_delisted_themes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetch_theme
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 fetch_partner_products
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
7.02
 fetch_recommended_themes
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 search_themes
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Class WPCom_Themes_Api.
4 * Retrieves themes from the WordPress.com themes API.
5 *
6 * @package automattic/jetpack-mu-wpcom
7 */
8
9/**
10 * Fetches themes from the WordPress.com themes API.
11 */
12class WPCom_Themes_Api {
13
14    /**
15     * The URL of the WordPress.com themes API.
16     */
17    const WP_COM_THEMES_API_URL = 'https://public-api.wordpress.com/wpcom/v2/themes?_envelope=1&page=1&number=1000';
18
19    /**
20     * The URL of the WordPress.com theme API.
21     */
22    const WP_COM_THEME_API_URL = 'https://public-api.wordpress.com/rest/v1.2/themes/%s?http_envelope=1';
23
24    /**
25     * The URL listing the partner tier, which is the one tier that is sold per theme.
26     */
27    const WP_COM_PARTNER_THEMES_API_URL = 'https://public-api.wordpress.com/rest/v1.2/themes?http_envelope=1&tier=partner&number=500';
28
29    /**
30     * Cache handler.
31     *
32     * @var WPCom_Themes_Cache $cache
33     */
34    private WPCom_Themes_Cache $cache;
35
36    /**
37     * Class constructor.
38     *
39     * @param WPCom_Themes_Cache $cache Cache handler.
40     */
41    public function __construct( WPCom_Themes_Cache $cache ) {
42        $this->cache = $cache;
43    }
44
45    /**
46     * Returns an array of themes fetched from the WordPress.com themes API.
47     * Caching is used to avoid fetching the themes on every request.
48     *
49     * @param string $cache_key Key of the cache where the API response will be cached.
50     * @param array  $params    Query params to pass to the API URL.
51     *
52     * @return array<stdClass> An array with all the WPCom themes.
53     */
54    protected function fetch_themes( string $cache_key, array $params = array() ): array {
55        $url = add_query_arg( $params, self::WP_COM_THEMES_API_URL );
56
57        return $this->cache->run_cached(
58            $cache_key,
59            function () use ( $url ) {
60                $body = $this->handle_request( $url );
61
62                // Null, not an empty array: run_cached() stores anything else.
63                return isset( $body->themes ) && is_array( $body->themes ) ? $body->themes : null;
64            }
65        ) ?? array();
66    }
67
68    /**
69     * Fetches the response from the given URL.
70     *
71     * @param string $url URL to fetch.
72     *
73     * @return ?stdClass Response body.
74     */
75    protected function handle_request( string $url ): ?stdClass {
76        $response = wp_remote_get( esc_url_raw( $url ) );
77        if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
78            $body_json = json_decode( wp_remote_retrieve_body( $response ) );
79
80            if ( isset( $body_json->body ) ) {
81                return $body_json->body;
82            }
83        }
84
85        return null;
86    }
87
88    /**
89     * Returns all the WP.com themes.
90     *
91     * @return array<stdClass> An array with all the WPCom themes.
92     */
93    public function fetch_all_non_delisted_themes(): array {
94        return $this->fetch_themes( 'wpcom-themes-all' );
95    }
96
97    /**
98     * Returns the WP.com theme with the given slug.
99     *
100     * @param string $slug Theme slug.
101     *
102     * @return stdClass|null A WPCom theme object or null if not found.
103     */
104    public function fetch_theme( string $slug ): ?stdClass {
105        $url = sprintf( self::WP_COM_THEME_API_URL, $slug );
106
107        $theme = $this->cache->run_cached(
108            'wpcom-themes-' . $slug,
109            fn() => $this->handle_request( $url )
110        );
111
112        if ( ! $theme || isset( $theme->error ) ) {
113            return null;
114        }
115
116        return $theme;
117    }
118
119    /**
120     * Returns the store products each partner theme is sold as, by theme slug.
121     *
122     * The catalog endpoint does not carry `product_details`, so the one tier that is sold per
123     * theme is read again from the endpoint that does. DOTCOM-18927.
124     *
125     * @return array<string, array<stdClass>> Product details, keyed by theme slug.
126     */
127    public function fetch_partner_products(): array {
128        return $this->cache->run_cached(
129            'wpcom-themes-partner-products',
130            function () {
131                $body = $this->handle_request( self::WP_COM_PARTNER_THEMES_API_URL );
132
133                if ( ! isset( $body->themes ) || ! is_array( $body->themes ) ) {
134                    return null;
135                }
136
137                $products = array();
138                foreach ( $body->themes as $theme ) {
139                    if ( isset( $theme->id ) && is_string( $theme->id ) && ! empty( $theme->product_details ) ) {
140                        $products[ $theme->id ] = (array) $theme->product_details;
141                    }
142                }
143
144                return $products;
145            }
146        ) ?? array();
147    }
148
149    /**
150     * Returns the collection of the recommended WP.com themes.
151     *
152     * @return array<stdClass> An array with all the recommended WPCom themes.
153     */
154    public function fetch_recommended_themes(): array {
155        return $this->fetch_themes(
156            'wpcom-themes-recommended',
157            array( 'collection' => 'recommended' )
158        );
159    }
160
161    /**
162     * Returns the WP.com themes that match the given search term.
163     *
164     * @param string $search Search term.
165     *
166     * @return array<stdClass> An array with all the matching WPCom themes.
167     */
168    public function search_themes( string $search ): array {
169        return $this->fetch_themes(
170            'wpcom-themes-search-' . md5( $search ),
171            array(
172                'search' => $search,
173                'sort'   => 'date',
174            )
175        );
176    }
177}