Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
2.27% covered (danger)
2.27%
2 / 88
0.00% covered (danger)
0.00%
0 / 8
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 / 24
0.00% covered (danger)
0.00%
0 / 1
56
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
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    if ( wpcomsh_is_theme_symlinked( $theme_slug ) ) {
81        error_log( "WPComSH: WP.com theme with slug: {$theme_slug} is already installed/symlinked." ); //phpcs:ignore
82
83        return new WP_Error( 'wpcom_theme_already_installed', 'The WP.com theme is already installed/symlinked.' );
84    }
85
86    $was_theme_symlinked = wpcomsh_symlink_theme( $theme_slug, $theme_type );
87
88    if ( is_wp_error( $was_theme_symlinked ) ) {
89        return $was_theme_symlinked;
90    }
91
92    wpcomsh_delete_theme_cache( $theme_slug );
93
94    // Skip the theme installation as we've "installed" (symlinked) it manually above.
95    add_filter(
96        'jetpack_wpcom_theme_install',
97        function () use ( $was_theme_symlinked ) {
98            return $was_theme_symlinked;
99        },
100        10,
101        2
102    );
103
104    // If the installed WPCom theme is a child theme, we need to symlink its parent theme as well.
105    if (
106        wpcomsh_is_wpcom_child_theme( $theme_slug ) &&
107        ! wpcomsh_is_theme_symlinked( wp_get_theme( $theme_slug )->get_template() )
108    ) {
109        $was_parent_theme_symlinked = wpcomsh_symlink_parent_theme( $theme_slug );
110
111        if ( ! $was_parent_theme_symlinked ) {
112            return new WP_Error( 'wpcom_theme_installation_failed', "Can't install specified WP.com theme. Check error log for more details." );
113        }
114    }
115
116    return true;
117}
118add_filter( 'jetpack_wpcom_theme_skip_download', 'wpcomsh_jetpack_wpcom_theme_skip_download', 10, 2 );
119
120/**
121 * Un-symlinks WP.com themes instead of deleting them.
122 *
123 * @param bool   $use_alternative_delete_method Whether to use the alternative method of deleting a WP.com theme.
124 * @param string $theme_slug                    Theme slug. If it is a WP.com theme it should be suffixed with `-wpcom`.
125 *
126 * @return bool
127 */
128function wpcomsh_jetpack_wpcom_theme_delete( $use_alternative_delete_method, $theme_slug ) {
129    if ( ! wpcomsh_is_wpcom_theme( $theme_slug ) || ! wpcomsh_is_theme_symlinked( $theme_slug ) ) {
130        return false;
131    }
132
133    $theme = wp_get_theme( $theme_slug );
134    if ( ! $theme->exists() ) {
135        return false;
136    }
137
138    $num_child_themes = wpcomsh_count_child_themes( $theme->get_stylesheet() );
139    if ( $num_child_themes > 0 ) {
140        // phpcs:ignore WordPress.PHP.DevelopmentFunctions
141        error_log( 'WPComSH: Cannot remove parent theme. It still has installed child themes.' );
142        return false;
143    }
144
145    $is_child_theme = wpcomsh_is_wpcom_child_theme( $theme_slug );
146
147    /*
148     * Remember how many child themes there are before we delete this one.
149     * That way, we have the same count later, whether the previous themes list was cached or not.
150     */
151    $num_themes_with_same_parent = 0;
152    if ( $is_child_theme ) {
153        $num_themes_with_same_parent = wpcomsh_count_child_themes( $theme->get_template() );
154    }
155
156    $was_theme_unsymlinked = wpcomsh_delete_symlinked_theme( $theme_slug );
157    if ( is_wp_error( $was_theme_unsymlinked ) ) {
158        return $was_theme_unsymlinked;
159    }
160
161    // If the theme was an only child, we need to unsymlink the parent theme as well.
162    if ( $is_child_theme && $num_themes_with_same_parent === 1 ) {
163        $parent_theme = $theme->get_template();
164        if ( wpcomsh_is_wpcom_theme( $parent_theme ) ) {
165            // phpcs:ignore WordPress.PHP.DevelopmentFunctions
166            error_log( 'WPComSH: Unsymlinking parent theme.' );
167
168            /*
169             * Ignore result because the child theme is already removed,
170             * and any problem should be logged by the deletion function.
171             */
172            wpcomsh_delete_symlinked_theme( $parent_theme );
173        }
174    }
175
176    return true;
177}
178add_filter( 'jetpack_wpcom_theme_delete', 'wpcomsh_jetpack_wpcom_theme_delete', 10, 2 );
179
180/**
181 * When a request is made to Jetpack Themes API, we need to distinguish between a WP.com theme
182 * and a WP.org theme in the response. This function adds/modifies the `theme_uri` field of a theme
183 * changing it to `https://wordpress.com/theme/{$theme_slug}` if a theme is a WP.com one.
184 *
185 * @param array $formatted_theme Array containing the Jetpack Themes API data to be sent to WP.com.
186 *
187 * @return array The original or modified theme info array.
188 */
189function wpcomsh_add_wpcom_suffix_to_theme_endpoint_response( $formatted_theme ) {
190    if ( empty( $formatted_theme['id'] ) ) {
191        return $formatted_theme;
192    }
193
194    $theme_slug    = $formatted_theme['id'];
195    $is_storefront = 'storefront' === $theme_slug;
196
197    if ( wpcomsh_is_theme_symlinked( $theme_slug ) && ! $is_storefront ) {
198        $formatted_theme['theme_uri'] = "https://wordpress.com/theme/{$theme_slug}";
199    }
200
201    return $formatted_theme;
202}
203add_filter( 'jetpack_format_theme_details', 'wpcomsh_add_wpcom_suffix_to_theme_endpoint_response' );
204
205/**
206 * Load a WordPress.com theme compat file, if it exists.
207 */
208function wpcomsh_load_theme_compat_file() {
209    if ( defined( 'WP_INSTALLING' ) && 'wp-activate.php' !== $GLOBALS['pagenow'] ) {
210        return;
211    }
212
213    // Many wpcom.php files call $themecolors directly. Ease the pain.
214    global $themecolors; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- included files might need the global.
215
216    $template_path   = get_template_directory();
217    $stylesheet_path = get_stylesheet_directory();
218    $file            = '/inc/wpcom.php';
219
220    // Look also in /includes as alternate location, since premium theme partners may use that convention.
221    if ( ! file_exists( $template_path . $file ) && ! file_exists( $stylesheet_path . $file ) ) {
222        $file = '/includes/wpcom.php';
223    }
224
225    // Include 'em. Child themes first, just like core.
226    if ( $template_path !== $stylesheet_path && file_exists( $stylesheet_path . $file ) ) {
227        include_once $stylesheet_path . $file;
228    }
229
230    if ( file_exists( $template_path . $file ) ) {
231        include_once $template_path . $file;
232    }
233}
234add_action( 'after_setup_theme', 'wpcomsh_load_theme_compat_file', 0 ); // Hook early so that after_setup_theme can still be used at default priority.
235
236/**
237 * Provides a favicon fallback in case it's undefined.
238 *
239 * @param string $url Site Icon URL.
240 * @return string Site Icon URL.
241 */
242function wpcomsh_site_icon_url( $url ) {
243    if ( empty( $url ) ) {
244        $url = 'https://s0.wp.com/i/webclip.png';
245    }
246
247    return $url;
248}
249add_filter( 'get_site_icon_url', 'wpcomsh_site_icon_url' );