Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
6.57% covered (danger)
6.57%
9 / 137
10.00% covered (danger)
10.00%
2 / 20
CRAP
n/a
0 / 0
wpcomsh_is_wpcom_theme
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_is_wpcom_premium_theme
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_is_wpcom_pub_theme
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_symlink_theme
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
wpcomsh_delete_theme_cache
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_is_theme_symlinked
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_delete_symlinked_theme
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_get_wpcom_theme_type
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_is_wpcom_child_theme
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
wpcomsh_count_child_themes
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
wpcomsh_symlink_parent_theme
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_get_atomic_site_id
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_get_atomic_client_id
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
wpcomsh_get_wpcom_active_subscriptions
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
20
wpcomsh_get_at_site_info
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
wpcomsh_is_xmlrpc_request_matching
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
16.67
wpcomsh_is_wp_rest_request_matching
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
56
wpcomsh_map_block_map_provider
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomsh_disable_fatal_error_emails
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_newsletter_categories_location
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * WPCOMSH functions file.
4 *
5 * @package wpcomsh
6 */
7
8/**
9 * Whether the theme is a wpcom theme.
10 *
11 * @param string $theme_slug Theme slug.
12 * @return bool
13 */
14function wpcomsh_is_wpcom_theme( $theme_slug ) {
15    return wpcomsh_is_wpcom_premium_theme( $theme_slug ) || wpcomsh_is_wpcom_pub_theme( $theme_slug );
16}
17
18/**
19 * Whether the theme is a premium wpcom theme.
20 *
21 * @param string $theme_slug Theme slug.
22 * @return bool
23 */
24function wpcomsh_is_wpcom_premium_theme( $theme_slug ) {
25    if (
26        ! defined( 'WPCOMSH_PREMIUM_THEMES_PATH' ) ||
27        ! file_exists( WPCOMSH_PREMIUM_THEMES_PATH )
28    ) {
29        // phpcs:ignore WordPress.PHP.DevelopmentFunctions
30        error_log(
31            "WPComSH: WPCom premium themes folder couldn't be located. " .
32            'Check whether the ' . WPCOMSH_PREMIUM_THEMES_PATH . ' constant points to the correct directory.'
33        );
34
35        return false;
36    }
37
38    return file_exists(
39        WPCOMSH_PREMIUM_THEMES_PATH . "/{$theme_slug}"
40    );
41}
42
43/**
44 * Whether the theme is a free wpcom theme.
45 *
46 * @param string $theme_slug Theme slug.
47 * @return bool
48 */
49function wpcomsh_is_wpcom_pub_theme( $theme_slug ) {
50    if (
51        ! defined( 'WPCOMSH_PUB_THEMES_PATH' ) ||
52        ! file_exists( WPCOMSH_PUB_THEMES_PATH )
53    ) {
54        // phpcs:ignore WordPress.PHP.DevelopmentFunctions
55        error_log(
56            "WPComSH: WPCom pub themes folder couldn't be located. " .
57            'Check whether the ' . WPCOMSH_PUB_THEMES_PATH . ' constant points to the correct directory.'
58        );
59
60        return false;
61    }
62
63    return file_exists(
64        WPCOMSH_PUB_THEMES_PATH . "/{$theme_slug}"
65    );
66}
67
68/**
69 * Symlinks a wpcom theme.
70 *
71 * @param string $theme_slug Theme slug.
72 * @param string $theme_type Type of theme.
73 * @return bool|WP_Error
74 */
75function wpcomsh_symlink_theme( $theme_slug, $theme_type ) {
76    /**
77     * We need to read from the themes_path constants and create the symlink using the themes_symlink constant.
78     *
79     * More context here:
80     * - p1487627624008111-slack-C2PDURDSL
81     * - p1713351585355929-slack-C7YPW6K40
82     */
83    $theme_link_to_path = get_theme_root() . '/' . $theme_slug;
84    $theme_read_path    = '';
85    $theme_symlink_path = '';
86    if ( WPCOMSH_PUB_THEME_TYPE === $theme_type ) {
87        $theme_read_path    = WPCOMSH_PUB_THEMES_PATH . "/$theme_slug";
88        $theme_symlink_path = WPCOMSH_PUB_THEMES_SYMLINK . "/$theme_slug";
89    } elseif ( WPCOMSH_PREMIUM_THEME_TYPE === $theme_type ) {
90        $theme_read_path    = WPCOMSH_PREMIUM_THEMES_PATH . "/$theme_slug";
91        $theme_symlink_path = WPCOMSH_PREMIUM_THEMES_SYMLINK . "/$theme_slug";
92    }
93
94    if ( file_exists( $theme_read_path ) && symlink( $theme_symlink_path, $theme_link_to_path ) ) {
95        return true;
96    }
97
98    $error_message = "Could not install theme $theme_slug.";
99    $debug_message = "$error_message Read directory: $theme_read_path, symlink directory: $theme_symlink_path, link to: $theme_link_to_path.";
100
101    error_log( 'WPComSH: ' . $debug_message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
102
103    return new WP_Error( 'error_symlinking_theme', $error_message );
104}
105
106/**
107 * Deletes cache of the passed theme.
108 *
109 * @param string $theme_slug Optional. Slug of the theme to delete cache for.
110 *                           Default: Current theme.
111 */
112function wpcomsh_delete_theme_cache( $theme_slug = null ) {
113    $theme = wp_get_theme( $theme_slug );
114
115    if ( $theme instanceof WP_Theme ) {
116        $theme->cache_delete();
117    }
118}
119
120/**
121 * Checks whether a theme (by theme slug) is symlinked in the themes' directory.
122 *
123 * @param string $theme_slug The slug of a theme.
124 * @return bool Whether a theme is symlinked in the themes' directory.
125 */
126function wpcomsh_is_theme_symlinked( $theme_slug ) {
127    $theme_root  = get_theme_root();
128    $theme_dir   = "$theme_root/$theme_slug";
129    $site_themes = scandir( $theme_root );
130
131    return in_array( $theme_slug, $site_themes, true ) && is_link( $theme_dir );
132}
133
134/**
135 * Deletes a symlinked theme.
136 *
137 * @param string $theme_slug The slug of a theme.
138 * @return bool|WP_Error True on success, WP_Error on error.
139 */
140function wpcomsh_delete_symlinked_theme( $theme_slug ) {
141    $theme_dir = get_theme_root() . "/$theme_slug";
142
143    if ( file_exists( $theme_dir ) && is_link( $theme_dir ) ) {
144        unlink( $theme_dir ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
145
146        return true;
147    }
148
149    // phpcs:ignore WordPress.PHP.DevelopmentFunctions
150    error_log(
151        "WPComSH: Can't delete the specified symlinked theme: the path or symlink doesn't exist."
152    );
153
154    return new WP_Error(
155        'error_deleting_symlinked_theme',
156        "Can't delete the specified symlinked theme: the path or symlink doesn't exist."
157    );
158}
159
160/**
161 * Returns a theme type.
162 *
163 * @param string $theme_slug The slug of a theme.
164 * @return false|string Theme type or false if not a wpcom theme.
165 */
166function wpcomsh_get_wpcom_theme_type( $theme_slug ) {
167    if ( wpcomsh_is_wpcom_premium_theme( $theme_slug ) ) {
168        return WPCOMSH_PREMIUM_THEME_TYPE;
169    } elseif ( wpcomsh_is_wpcom_pub_theme( $theme_slug ) ) {
170        return WPCOMSH_PUB_THEME_TYPE;
171    }
172
173    return false;
174}
175
176/**
177 * Returns whether the theme is a child theme.
178 *
179 * @param string $theme_slug Slug of the theme to check. Default: Active theme.
180 * @return bool
181 */
182function wpcomsh_is_wpcom_child_theme( $theme_slug = null ) {
183    $theme = wp_get_theme( $theme_slug );
184
185    return $theme->get_stylesheet() !== $theme->get_template();
186}
187
188/**
189 * Count the number of child themes with the specified template.
190 *
191 * @param string $template The theme template name.
192 *
193 * @return int
194 */
195function wpcomsh_count_child_themes( $template ) {
196    $child_count = 0;
197
198    foreach ( wp_get_themes() as $theme ) {
199        if (
200            $theme->get_template() === $template &&
201            $theme->get_stylesheet() !== $theme->get_template()
202        ) {
203            ++$child_count;
204        }
205    }
206
207    return $child_count;
208}
209
210/**
211 * Symlinks the theme's parent if it's a child theme.
212 *
213 * @param string $stylesheet Theme slug.
214 * @return bool|WP_Error
215 */
216function wpcomsh_symlink_parent_theme( $stylesheet ) {
217    $theme    = wp_get_theme( $stylesheet );
218    $template = $theme->get_template();
219
220    if ( $template === $stylesheet ) {
221        // phpcs:ignore WordPress.PHP.DevelopmentFunctions
222        error_log( "WPComSH: Can't symlink parent theme. Current theme is not a child theme." );
223
224        return false;
225    }
226
227    // phpcs:ignore WordPress.PHP.DevelopmentFunctions
228    error_log( 'WPComSH: Symlinking parent theme.' );
229
230    return wpcomsh_symlink_theme( $template, wpcomsh_get_wpcom_theme_type( $template ) );
231}
232
233/**
234 * Returns the Atomic site ID.
235 *
236 * @return int
237 */
238function wpcomsh_get_atomic_site_id() {
239    if ( defined( 'ATOMIC_SITE_ID' ) ) {
240        return (int) ATOMIC_SITE_ID;
241    }
242
243    $atomic_site_id = apply_filters( 'wpcomsh_get_atomic_site_id', 0 );
244    if ( ! empty( $atomic_site_id ) ) {
245        return (int) $atomic_site_id;
246    }
247
248    return 0;
249}
250
251/**
252 * Returns the Atomic client ID.
253 *
254 * @return int
255 */
256function wpcomsh_get_atomic_client_id() {
257    if ( defined( 'ATOMIC_CLIENT_ID' ) ) {
258        return (int) ATOMIC_CLIENT_ID;
259    }
260
261    $atomic_client_id = apply_filters( 'wpcomsh_get_atomic_client_id', 0 );
262    if ( ! empty( $atomic_client_id ) ) {
263        return (int) $atomic_client_id;
264    }
265
266    return 0;
267}
268
269/**
270 * Returns an array of active WordPress.com subscriptions. The array keys are the product slugs.
271 *
272 * @return array
273 */
274function wpcomsh_get_wpcom_active_subscriptions() {
275    $persistent_data = new Atomic_Persistent_Data();
276
277    if ( ! $persistent_data->WPCOM_PURCHASES ) { // phpcs:ignore WordPress.NamingConventions
278        return array();
279    }
280
281    $wpcom_purchases = json_decode( $persistent_data->WPCOM_PURCHASES ); // phpcs:ignore WordPress.NamingConventions
282
283    // If ( for any reason ) $wpcom_purchases is not an array, return early an empty array so that array_reduce doesn't throw an error.
284    if ( ! is_array( $wpcom_purchases ) ) {
285        return array();
286    }
287
288    return array_reduce(
289        $wpcom_purchases,
290        function ( $assoc_array, $purchase ) {
291            /**
292             * Check if the billing_product_slug exists, if not, revert to parsing the store product slug.
293             * This happens for sites that don't have the APD updated to the new format.
294             */
295            if ( isset( $purchase->billing_product_slug ) ) {
296                $product_slug = $purchase->billing_product_slug;
297            } else {
298                // 1. Remove _monthly or _yearly ( mainly found in marketplace plugins ).
299                // 2. Transform to product slug pattern with dashes (billing product slug).
300                $product_slug = preg_replace( array( '/(_monthly|_yearly)$/', '/_/' ), array( '', '-' ), $purchase->product_slug );
301            }
302
303            $assoc_array[ $product_slug ] = $purchase;
304
305            return $assoc_array;
306        },
307        array()
308    );
309}
310add_filter( 'pre_option_wpcom_active_subscriptions', 'wpcomsh_get_wpcom_active_subscriptions' );
311
312/**
313 * Get Atomic site information.
314 *
315 * @return array
316 */
317function wpcomsh_get_at_site_info() {
318    $at_site_info_file = sys_get_temp_dir() . '/.at-site-info';
319
320    if ( ! is_file( $at_site_info_file ) ) {
321        return array();
322    }
323
324    $site_info_json = file_get_contents( $at_site_info_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions
325
326    if ( empty( $site_info_json ) ) {
327        return array();
328    }
329
330    $site_info = json_decode( $site_info_json, true );
331    if ( empty( $site_info ) ) {
332        return array();
333    }
334
335    return $site_info;
336}
337
338/**
339 * Whether the current request is an XML-RPC request from Calypso to install a theme or plugin.
340 *
341 * @param string $path_regex Regular expression of paths to allow.
342 * @return bool
343 */
344function wpcomsh_is_xmlrpc_request_matching( $path_regex ) {
345    // Return early for all non-API requests.
346    if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
347        return false;
348    }
349
350    // Return early-ish when it's not a verified XML-RPC request.
351    if (
352        ! method_exists( 'Automattic\Jetpack\Connection\Manager', 'verify_xml_rpc_signature' ) ||
353        ! ( new Automattic\Jetpack\Connection\Manager() )->verify_xml_rpc_signature() ) {
354        return false;
355    }
356
357    return class_exists( 'WPCOM_JSON_API' ) && preg_match( $path_regex, WPCOM_JSON_API::$self->path );
358}
359
360/**
361 * Check if we are handling a WordPress core REST v2 API request where the API path matches
362 * the regular expression in $path_regex and the HTTP method(s) in $request_method.
363 *
364 * @param string          $path_regex      A regular expression for the requested path, which should include the REST prefix available from {@see rest_get_url_prefix()}.
365 * @param string|string[] $request_method  The expected HTTP method(s) of the request, e.g. GET|POST|PUT|DELETE or array( 'PUT', 'POST' ).
366 * @return bool                            Whether the incoming request matches the supplied path and method(s).
367 */
368function wpcomsh_is_wp_rest_request_matching( $path_regex, $request_method = 'GET' ) {
369    if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
370        return false;
371    }
372
373    $request_methods = is_array( $request_method ) ? $request_method : array( $request_method );
374
375    if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || ! in_array( $_SERVER['REQUEST_METHOD'], $request_methods, true ) ) {
376        return false;
377    }
378
379    if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
380        return false;
381    }
382
383    $rest_path = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
384
385    return 1 === preg_match( $path_regex, $rest_path );
386}
387
388/**
389 * Returns the map provider the map block should use.
390 *
391 * @return string
392 */
393function wpcomsh_map_block_map_provider() {
394    return 'mapkit';
395}
396
397add_filter( 'wpcom_map_block_map_provider', 'wpcomsh_map_block_map_provider', 10, 0 );
398
399/**
400 * Disabled fatal error emails if the option is set.
401 *
402 * @param array $email The email arguments.
403 *
404 * @return array The email arguments.
405 */
406function wpcomsh_disable_fatal_error_emails( $email ) {
407    if ( get_option( 'wpcomsh_disable_fatal_error_emails', false ) ) {
408        $email['to'] = '';
409    }
410    return $email;
411}
412
413add_filter( 'recovery_mode_email', 'wpcomsh_disable_fatal_error_emails' );
414
415/**
416 * Returns the location where newsletter categories should appear
417 *
418 * @return string
419 */
420function wpcomsh_newsletter_categories_location() {
421    return 'modal';
422}
423
424add_filter( 'wpcom_newsletter_categories_location', 'wpcomsh_newsletter_categories_location', 10, 0 );
425
426/**
427 * Enables new likes layout on Atomic.
428 */
429add_filter( 'likes_new_layout', '__return_true' );
430
431/**
432 * Disable email notification when a user's email address is changed.
433 *
434 * @see https://github.com/Automattic/wp-calypso/issues/100279
435 */
436add_filter( 'send_email_change_email', '__return_false' );