Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.33% covered (danger)
16.33%
16 / 98
7.69% covered (danger)
7.69%
1 / 13
CRAP
n/a
0 / 0
wpcomsh_get_wpcom_themes_service_instance
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_remove_dot_org_themes_based_on_plan
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_keep_only_wpcom_themes
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
wpcomsh_popular_wpcom_themes_api_result
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_latest_wpcom_themes_api_result
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_block_themes_wpcom_themes_api_result
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_search_wpcom_themes_api_result
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_feature_filter_wpcom_themes_api_result
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
wpcomsh_theme_information_wpcom_themes_api_result
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_remove_symlink_wp_error
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_theme_install_by_symlink
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
56
wpcomsh_delete_managed_wpcom_theme
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_include_themes_creation_date
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Registers a filter for the themes API result to add WPCom themes.
4 *
5 * @package wpcom-themes
6 */
7
8/**
9 * Loads the WPCom themes service instance.
10 *
11 * @return WPCom_Themes_Service
12 */
13function wpcomsh_get_wpcom_themes_service_instance(): WPCom_Themes_Service {
14    static $instance;
15    if ( ! $instance ) {
16        // Load dependencies.
17        require_once __DIR__ . '/includes/class-wpcom-themes-merger.php';
18        require_once __DIR__ . '/includes/class-wpcom-themes-service.php';
19
20        // Resolve and Inject dependencies.
21        $mapper               = new WPCom_Themes_Mapper();
22        $merger               = new WPCom_Themes_Merger();
23        $cache                = new WPCom_Themes_Cache();
24        $api                  = new WPCom_Themes_Api( $cache );
25        $wpcom_themes_service = new WPCom_Themes_Service( $api, $mapper, $merger );
26
27        $instance = $wpcom_themes_service;
28    }
29
30    return $instance;
31}
32
33/**
34 * Perform necessary checks to remove dot org themes based on the plan and other features.
35 *
36 * @param mixed $res The result object.
37 *
38 * @return mixed|stdClass
39 */
40function wpcomsh_remove_dot_org_themes_based_on_plan( $res ) {
41    if ( wpcom_site_has_feature( WPCOM_Features::COMMUNITY_THEMES ) ) {
42        return $res;
43    }
44
45    return wpcomsh_keep_only_wpcom_themes( $res );
46}
47add_filter( 'themes_api_result', 'wpcomsh_remove_dot_org_themes_based_on_plan', 0, 1 );
48
49/**
50 * Drops the themes that came from WordPress.org out of a themes API result.
51 *
52 * @param mixed $res The result object.
53 *
54 * @return mixed|stdClass
55 */
56function wpcomsh_keep_only_wpcom_themes( $res ) {
57    // A single theme's details, rather than a list to filter.
58    if ( ! isset( $res->themes ) || ! is_array( $res->themes ) ) {
59        return $res;
60    }
61
62    $ours = array_values(
63        array_filter(
64            $res->themes,
65            function ( $theme ) {
66                return ! empty( $theme->is_wpcom_theme );
67            }
68        )
69    );
70
71    // Our own themes are already the ones this site may install, and the total counts every page
72    // rather than this one, so a result made of them is left as it is.
73    if ( count( $ours ) === count( $res->themes ) ) {
74        return $res;
75    }
76
77    /*
78     * The rest came from WordPress.org, which this plan excludes. Clearing the total as well lets
79     * the filters that run next set it from their own list, which is what they expect.
80     */
81    $res->themes = $ours;
82
83    if ( isset( $res->info['results'] ) ) {
84        $res->info['results'] = count( $ours );
85    }
86
87    return $res;
88}
89
90/**
91 * Process the themes API result and add the recommended WPCom themes to the Popular tab.
92 *
93 * @param mixed  $res    The result object.
94 * @param string $action The action.
95 * @param mixed  $args   The arguments.
96 *
97 * @return mixed|stdClass
98 */
99function wpcomsh_popular_wpcom_themes_api_result( $res, string $action, $args ) {
100    // Pre-requisites checks.
101    $browse = $args->browse ?? '';
102    if ( 'query_themes' !== $action || 'popular' !== $browse ) {
103        return $res;
104    }
105
106    $wpcom_themes_service = wpcomsh_get_wpcom_themes_service_instance();
107
108    // Add results to the resulting array.
109    return $wpcom_themes_service->filter_themes_api_result_recommended( $res );
110}
111add_filter( 'themes_api_result', 'wpcomsh_popular_wpcom_themes_api_result', 1, 3 );
112
113/**
114 * Process the themes API result and add the latest WPCom themes to the Latest tab.
115 *
116 * @param mixed  $res    The result object.
117 * @param string $action The action.
118 * @param mixed  $args   The arguments.
119 *
120 * @return mixed|stdClass
121 */
122function wpcomsh_latest_wpcom_themes_api_result( $res, string $action, $args ) {
123    // Pre-requisites checks.
124    $browse = $args->browse ?? '';
125    if ( 'query_themes' !== $action || 'new' !== $browse ) {
126        return $res;
127    }
128
129    $wpcom_themes_service = wpcomsh_get_wpcom_themes_service_instance();
130
131    // Add results to the resulting array.
132    return $wpcom_themes_service->filter_themes_api_result_latest( $res );
133}
134add_filter( 'themes_api_result', 'wpcomsh_latest_wpcom_themes_api_result', 1, 3 );
135
136/**
137 * Process the themes API result and add the WPCom block themes to the Block themes tab.
138 *
139 * @param mixed  $res    The result object.
140 * @param string $action The action.
141 * @param mixed  $args   The arguments.
142 *
143 * @return mixed|stdClass
144 */
145function wpcomsh_block_themes_wpcom_themes_api_result( $res, string $action, $args ) {
146    // Pre-requisites checks.
147    $tag = $args->tag ?? '';
148    if ( 'query_themes' !== $action || 'full-site-editing' !== $tag ) {
149        return $res;
150    }
151
152    $wpcom_themes_service = wpcomsh_get_wpcom_themes_service_instance();
153
154    // Add results to the resulting array.
155    return $wpcom_themes_service->filter_themes_api_result_block_themes( $res );
156}
157add_filter( 'themes_api_result', 'wpcomsh_block_themes_wpcom_themes_api_result', 1, 3 );
158
159/**
160 * Process the themes API result and add WPCom themes when using the search.
161 *
162 * @param mixed  $res    The result object.
163 * @param string $action The action.
164 * @param mixed  $args   The arguments.
165 *
166 * @return mixed|stdClass
167 */
168function wpcomsh_search_wpcom_themes_api_result( $res, string $action, $args ) {
169    // Pre-requisites checks.
170    $search = $args->search ?? '';
171    if ( 'query_themes' !== $action || '' === $search ) {
172        return $res;
173    }
174
175    $wpcom_themes_service = wpcomsh_get_wpcom_themes_service_instance();
176
177    // Add results to the resulting array.
178    return $wpcom_themes_service->filter_themes_api_result_search( $res, $search );
179}
180add_filter( 'themes_api_result', 'wpcomsh_search_wpcom_themes_api_result', 1, 3 );
181
182/**
183 * Process the themes API result and add WPCom themes when using the filter feature.
184 *
185 * @param mixed  $res    The result object.
186 * @param string $action The action.
187 * @param mixed  $args   The arguments.
188 *
189 * @return mixed|stdClass
190 */
191function wpcomsh_feature_filter_wpcom_themes_api_result( $res, string $action, $args ) {
192    // Pre-requisites checks.
193    $tags = $args->tag ?? array();
194    if ( 'query_themes' !== $action || ! $tags ) {
195        return $res;
196    }
197
198    if ( ! is_array( $tags ) ) {
199        $tags = array( $tags );
200    }
201
202    $wpcom_themes_service = wpcomsh_get_wpcom_themes_service_instance();
203
204    // Add results to the resulting array.
205    return $wpcom_themes_service->filter_themes_api_result_feature_filter( $res, $tags );
206}
207add_filter( 'themes_api_result', 'wpcomsh_feature_filter_wpcom_themes_api_result', 1, 3 );
208
209/**
210 * Process the themes API result theme_information for WPCom themes if needed.
211 *
212 * @param mixed  $res    The result object.
213 * @param string $action The action.
214 * @param mixed  $args   The arguments.
215 *
216 * @return mixed|stdClass|WP_Error|null
217 */
218function wpcomsh_theme_information_wpcom_themes_api_result( $res, string $action, $args ) {
219    // Pre-requisites checks.
220    if ( 'theme_information' !== $action ) {
221        return $res;
222    }
223
224    $wpcom_themes_service = wpcomsh_get_wpcom_themes_service_instance();
225
226    return $wpcom_themes_service->get_theme( $args->slug ) ?? $res;
227}
228add_filter( 'themes_api_result', 'wpcomsh_theme_information_wpcom_themes_api_result', 1, 3 );
229
230/**
231 * Remove the WP_Error object from the WP_Error object.
232 *
233 * @param string   $code    The error code.
234 * @param string   $message The error message.
235 * @param mixed    $data    The error data.
236 * @param WP_Error $error   The WP_Error object.
237 *
238 * @return void
239 */
240function wpcomsh_remove_symlink_wp_error( $code, $message, $data, WP_Error $error ) {
241    if ( 'wpcomsh_theme_install_symlink' === $code ) {
242        $error->remove( 'wpcomsh_theme_install_symlink' );
243    }
244}
245
246/**
247 * Install WPCom themes by creating a symlink instead of downloading the theme package.
248 *
249 * @param mixed  $reply    The result object.
250 * @param string $package  The package to install.
251 * @param mixed  $upgrader The upgrader component.
252 *
253 * @return bool|mixed|WP_Error
254 */
255function wpcomsh_theme_install_by_symlink( $reply, $package, $upgrader ) {
256    // Pre-requisites checks.
257    if ( ! $package || ! is_string( $package ) || ! $upgrader instanceof Theme_Upgrader ) {
258        return $reply;
259    }
260
261    $wpcom_themes_service = wpcomsh_get_wpcom_themes_service_instance();
262    $wpcom_theme          = $wpcom_themes_service->get_theme( $package );
263
264    if ( ! $wpcom_theme ) {
265        return $reply;
266    }
267
268    $upgrader->skin->feedback( 'installing_package' );
269    $result = wpcomsh_jetpack_wpcom_theme_skip_download( false, $wpcom_theme->slug );
270
271    if ( is_wp_error( $result ) && $result->get_error_code() !== 'wpcom_theme_already_installed' ) {
272        $upgrader->skin->feedback( 'process_failed' );
273        // The internal state and return values are WP_Errors.
274        $upgrader->result = $result;
275
276        return $result;
277    }
278
279    // Set the result to the theme slug to indicate success and so that the skin can reference it.
280    $result           = array(
281        'destination_name' => $wpcom_theme->slug,
282    );
283    $upgrader->result = $result;
284    // Skin uses both the upgrader and its own result which should be the same.
285    $upgrader->skin->result = $result;
286    $upgrader->skin->feedback( 'process_success' );
287
288    // Symlink errors are not really errors so let's remove them. Some clients inspect the skin errors to determine the success of the operation.
289    add_action( 'wp_error_added', 'wpcomsh_remove_symlink_wp_error', 0, 4 );
290
291    // We need to return a WP_Error to short circuit the installation process.
292    return new WP_Error( 'wpcomsh_theme_install_symlink' );
293}
294add_filter( 'upgrader_pre_download', 'wpcomsh_theme_install_by_symlink', 0, 4 );
295
296/**
297 * Remove the symlink created for the WPCom theme when the theme is deleted.
298 *
299 * @param string $stylesheet The theme stylesheet.
300 *
301 * @return void Return early if the theme is not a WPCom theme.
302 */
303function wpcomsh_delete_managed_wpcom_theme( string $stylesheet ) {
304    if ( wpcomsh_is_theme_symlinked( $stylesheet ) ) {
305        wpcomsh_jetpack_wpcom_theme_delete( false, $stylesheet );
306    }
307}
308add_action( 'delete_theme', 'wpcomsh_delete_managed_wpcom_theme' );
309
310/**
311 * Include the creation date in the themes API response.
312 *
313 * @param array $args The arguments.
314 *
315 * @return array The arguments with the creation time included.
316 */
317function wpcomsh_include_themes_creation_date( $args ) {
318    $args['fields']['creation_time'] = true;
319
320    return $args;
321}
322add_filter( 'install_themes_table_api_args_new', 'wpcomsh_include_themes_creation_date' );
323add_filter( 'install_themes_table_api_args_search', 'wpcomsh_include_themes_creation_date' );