Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
2.41% covered (danger)
2.41%
2 / 83
0.00% covered (danger)
0.00%
0 / 9
CRAP
n/a
0 / 0
wpcomsh_map_caps
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
7.46
wpcomsh_remove_theme_delete_button
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_handle_atomic_premium_theme_option
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_jetpack_wpcom_theme_skip_download
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_jetpack_wpcom_theme_delete
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
110
wpcomsh_add_wpcom_suffix_to_theme_endpoint_response
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
wpcomsh_load_theme_compat_file
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
72
wpcomsh_site_icon_url
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_admin_bar_hide_default_site_icon
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Managed themes file.
4 *
5 * @package wpcomsh
6 */
7
8/**
9 * Filters a user's capabilities depending on specific context and/or privilege.
10 *
11 * @param string[] $caps Primitive capabilities required of the user.
12 * @param string   $cap  Capability being checked.
13 * @return string[]
14 */
15function wpcomsh_map_caps( $caps, $cap ) {
16    if ( 'edit_themes' === $cap ) {
17        $theme = wp_get_theme();
18
19        if ( wpcomsh_is_wpcom_premium_theme( $theme->get_stylesheet() ) && 'Automattic' !== $theme->get( 'Author' ) ) {
20            $caps[] = 'do_not_allow';
21        }
22    }
23
24    return $caps;
25}
26add_action( 'map_meta_cap', 'wpcomsh_map_caps', 10, 2 );
27
28/**
29 * Removes theme delete button.
30 *
31 * @param array $prepared_themes Prepared themes.
32 *
33 * @return array
34 */
35function wpcomsh_remove_theme_delete_button( $prepared_themes ) {
36    foreach ( $prepared_themes as $theme_slug => $theme_data ) {
37        if ( wpcomsh_is_symlinked_storefront_theme( $theme_slug ) ) {
38            $prepared_themes[ $theme_slug ]['actions']['delete'] = '';
39        }
40    }
41
42    return $prepared_themes;
43}
44add_filter( 'wp_prepare_themes_for_js', 'wpcomsh_remove_theme_delete_button' );
45
46/**
47 * Returns the value for the `at_wpcom_premium_theme` option, which
48 * makes sure a stylesheet is returned only if the current theme has been
49 * symlinked and is a WP.com premium theme.
50 *
51 * @return string|bool The WP.com premium theme stylesheet or false if theme is not linked or a not premium theme.
52 */
53function wpcomsh_handle_atomic_premium_theme_option() {
54    $stylesheet = wp_get_theme()->get_stylesheet();
55
56    if ( wpcomsh_is_theme_symlinked( $stylesheet ) && wpcomsh_is_wpcom_premium_theme( $stylesheet ) ) {
57        return sprintf( 'premium/%s', $stylesheet );
58    }
59
60    return false;
61}
62add_filter( 'pre_option_at_wpcom_premium_theme', 'wpcomsh_handle_atomic_premium_theme_option' );
63
64/**
65 * Symlinks WP.com themes instead of downloading them.
66 *
67 * @param bool   $skip_download_filter_result Whether to skip the standard method of downloading and validating a WP.com theme.
68 * @param string $theme_slug                  Theme slug. If it is a WP.com theme it should be suffixed with `-wpcom`.
69 *
70 * @return bool|\WP_Error
71 */
72function wpcomsh_jetpack_wpcom_theme_skip_download( $skip_download_filter_result, $theme_slug ) {
73    $theme_type = wpcomsh_get_wpcom_theme_type( $theme_slug );
74
75    // If we are dealing with a non WPCom theme, don't interfere.
76    if ( ! $theme_type ) {
77        return false;
78    }
79
80    $was_theme_symlinked = wpcomsh_install_managed_theme( $theme_slug, $theme_type );
81
82    if ( true !== $was_theme_symlinked ) {
83        return $was_theme_symlinked;
84    }
85
86    // Skip the theme installation as we've "installed" (symlinked) it manually above.
87    add_filter(
88        'jetpack_wpcom_theme_install',
89        function () use ( $was_theme_symlinked ) {
90            return $was_theme_symlinked;
91        },
92        10,
93        2
94    );
95
96    return true;
97}
98add_filter( 'jetpack_wpcom_theme_skip_download', 'wpcomsh_jetpack_wpcom_theme_skip_download', 10, 2 );
99
100/**
101 * Un-symlinks WP.com themes instead of deleting them.
102 *
103 * @param bool   $use_alternative_delete_method Whether to use the alternative method of deleting a WP.com theme.
104 * @param string $theme_slug                    Theme slug. If it is a WP.com theme it should be suffixed with `-wpcom`.
105 *
106 * @return bool
107 */
108function wpcomsh_jetpack_wpcom_theme_delete( $use_alternative_delete_method, $theme_slug ) {
109    if ( ! wpcomsh_is_wpcom_theme( $theme_slug ) || ! wpcomsh_is_theme_symlinked( $theme_slug ) ) {
110        return false;
111    }
112
113    $theme = wp_get_theme( $theme_slug );
114    if ( ! $theme->exists() ) {
115        return false;
116    }
117
118    $num_child_themes = wpcomsh_count_child_themes( $theme->get_stylesheet() );
119    if ( $num_child_themes > 0 ) {
120        // phpcs:ignore WordPress.PHP.DevelopmentFunctions
121        error_log( 'WPComSH: Cannot remove parent theme. It still has installed child themes.' );
122        return false;
123    }
124
125    $is_child_theme = wpcomsh_is_wpcom_child_theme( $theme_slug );
126
127    /*
128     * Remember how many child themes there are before we delete this one.
129     * That way, we have the same count later, whether the previous themes list was cached or not.
130     */
131    $num_themes_with_same_parent = 0;
132    if ( $is_child_theme ) {
133        $num_themes_with_same_parent = wpcomsh_count_child_themes( $theme->get_template() );
134    }
135
136    $was_theme_unsymlinked = wpcomsh_delete_symlinked_theme( $theme_slug );
137    if ( is_wp_error( $was_theme_unsymlinked ) ) {
138        return $was_theme_unsymlinked;
139    }
140
141    // If the theme was an only child, we need to unsymlink the parent theme as well.
142    if ( $is_child_theme && $num_themes_with_same_parent === 1 ) {
143        $parent_theme = $theme->get_template();
144        if ( wpcomsh_is_wpcom_theme( $parent_theme ) ) {
145            // phpcs:ignore WordPress.PHP.DevelopmentFunctions
146            error_log( 'WPComSH: Unsymlinking parent theme.' );
147
148            /*
149             * Ignore result because the child theme is already removed,
150             * and any problem should be logged by the deletion function.
151             */
152            wpcomsh_delete_symlinked_theme( $parent_theme );
153        }
154    }
155
156    return true;
157}
158add_filter( 'jetpack_wpcom_theme_delete', 'wpcomsh_jetpack_wpcom_theme_delete', 10, 2 );
159
160/**
161 * When a request is made to Jetpack Themes API, we need to distinguish between a WP.com theme
162 * and a WP.org theme in the response. This function adds/modifies the `theme_uri` field of a theme
163 * changing it to `https://wordpress.com/theme/{$theme_slug}` if a theme is a WP.com one.
164 *
165 * @param array $formatted_theme Array containing the Jetpack Themes API data to be sent to WP.com.
166 *
167 * @return array The original or modified theme info array.
168 */
169function wpcomsh_add_wpcom_suffix_to_theme_endpoint_response( $formatted_theme ) {
170    if ( empty( $formatted_theme['id'] ) ) {
171        return $formatted_theme;
172    }
173
174    $theme_slug    = $formatted_theme['id'];
175    $is_storefront = 'storefront' === $theme_slug;
176
177    if ( wpcomsh_is_theme_symlinked( $theme_slug ) && ! $is_storefront ) {
178        $formatted_theme['theme_uri'] = "https://wordpress.com/theme/{$theme_slug}";
179    }
180
181    return $formatted_theme;
182}
183add_filter( 'jetpack_format_theme_details', 'wpcomsh_add_wpcom_suffix_to_theme_endpoint_response' );
184
185/**
186 * Load a WordPress.com theme compat file, if it exists.
187 */
188function wpcomsh_load_theme_compat_file() {
189    if ( defined( 'WP_INSTALLING' ) && 'wp-activate.php' !== $GLOBALS['pagenow'] ) {
190        return;
191    }
192
193    // Many wpcom.php files call $themecolors directly. Ease the pain.
194    global $themecolors; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- included files might need the global.
195
196    $template_path   = get_template_directory();
197    $stylesheet_path = get_stylesheet_directory();
198    $file            = '/inc/wpcom.php';
199
200    // Look also in /includes as alternate location, since premium theme partners may use that convention.
201    if ( ! file_exists( $template_path . $file ) && ! file_exists( $stylesheet_path . $file ) ) {
202        $file = '/includes/wpcom.php';
203    }
204
205    // Include 'em. Child themes first, just like core.
206    if ( $template_path !== $stylesheet_path && file_exists( $stylesheet_path . $file ) ) {
207        include_once $stylesheet_path . $file;
208    }
209
210    if ( file_exists( $template_path . $file ) ) {
211        include_once $template_path . $file;
212    }
213}
214add_action( 'after_setup_theme', 'wpcomsh_load_theme_compat_file', 0 ); // Hook early so that after_setup_theme can still be used at default priority.
215
216/**
217 * The default site icon served when a site doesn't have a custom one.
218 */
219const WPCOMSH_DEFAULT_SITE_ICON_URL = 'https://s0.wp.com/i/webclip.png';
220
221/**
222 * Provides a favicon fallback in case it's undefined.
223 *
224 * @param string $url Site Icon URL.
225 * @return string Site Icon URL.
226 */
227function wpcomsh_site_icon_url( $url ) {
228    if ( empty( $url ) ) {
229        $url = WPCOMSH_DEFAULT_SITE_ICON_URL;
230    }
231
232    return $url;
233}
234add_filter( 'get_site_icon_url', 'wpcomsh_site_icon_url' );
235
236/**
237 * Hides the site icon in the admin bar when the site has no custom site icon.
238 *
239 * This effectively ignores the favicon fallback provided by the
240 * `wpcomsh_site_icon_url()` function above.
241 *
242 * @param bool $show Whether to show site icons in the admin bar.
243 * @return bool Whether to show site icons in the admin bar.
244 */
245function wpcomsh_admin_bar_hide_default_site_icon( $show ) {
246    if ( $show && WPCOMSH_DEFAULT_SITE_ICON_URL === get_site_icon_url() ) {
247        return false;
248    }
249    return $show;
250}
251add_filter( 'wp_admin_bar_show_site_icons', 'wpcomsh_admin_bar_hide_default_site_icon' );