Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_JSON_API_Themes_Install_Endpoint
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 4
756
0.00% covered (danger)
0.00%
0 / 1
 install
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
132
 validate_themes
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
156
 is_installed_theme
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 download_wpcom_theme_to_file
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3use Automattic\Jetpack\Automatic_Install_Skin;
4use Automattic\Jetpack\Connection\Client;
5
6if ( ! defined( 'ABSPATH' ) ) {
7    exit( 0 );
8}
9
10require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
11require_once ABSPATH . 'wp-admin/includes/file.php';
12
13/**
14 * Themes install endpoint class.
15 *
16 * POST  /sites/%s/themes/%s/install
17 *
18 * @phan-constructor-used-for-side-effects
19 */
20class Jetpack_JSON_API_Themes_Install_Endpoint extends Jetpack_JSON_API_Themes_Endpoint {
21
22    /**
23     * Needed capabilities.
24     *
25     * @var string
26     */
27    protected $needed_capabilities = 'install_themes';
28
29    /**
30     * The action.
31     *
32     * @var string
33     */
34    protected $action = 'install';
35
36    /**
37     * Download links.
38     *
39     * @var array
40     */
41    protected $download_links = array();
42
43    /**
44     * Install the theme.
45     *
46     * @return bool|WP_Error
47     */
48    protected function install() {
49
50        foreach ( $this->themes as $theme ) {
51
52            /**
53             * Filters whether to use an alternative process for installing a WordPress.com theme.
54             * The alternative process can be executed during the filter.
55             *
56             * The filter can also return an instance of WP_Error; in which case the endpoint response will
57             * contain this error.
58             *
59             * @module json-api
60             *
61             * @since 4.4.2
62             *
63             * @param bool   $use_alternative_install_method Whether to use the alternative method of installing
64             *                                               a WPCom theme.
65             * @param string $theme_slug                     Theme name (slug). If it is a WPCom theme,
66             *                                               it should be suffixed with `-wpcom`.
67             */
68            $result = apply_filters( 'jetpack_wpcom_theme_install', false, $theme );
69
70            $skin     = null;
71            $upgrader = null;
72            $link     = null;
73
74            // If the alternative install method was not used, use the standard method.
75            if ( ! $result ) {
76                $skin     = new Automatic_Install_Skin();
77                $upgrader = new Theme_Upgrader( $skin );
78
79                $link   = $this->download_links[ $theme ];
80                $result = $upgrader->install( $link );
81            }
82
83            if ( file_exists( $link ) ) {
84                // Delete if link was tmp local file
85                wp_delete_file( $link );
86            }
87
88            if ( ! $this->bulk && is_wp_error( $result ) ) {
89                return $result;
90            }
91
92            if ( ! $result ) {
93                $error                        = __( 'An unknown error occurred during installation', 'jetpack' );
94                $this->log[ $theme ]['error'] = $error;
95            } elseif ( ! self::is_installed_theme( $theme ) ) {
96                $error                        = __( 'There was an error installing your theme', 'jetpack' );
97                $this->log[ $theme ]['error'] = $error;
98            } elseif ( $upgrader ) {
99                $this->log[ $theme ][] = $upgrader->skin->get_upgrade_messages();
100            }
101        }
102
103        if ( ! $this->bulk && isset( $error ) ) {
104            return new WP_Error( 'install_error', $error, 400 );
105        }
106
107        return true;
108    }
109
110    /**
111     * Validate the themes.
112     *
113     * @return bool|WP_Error
114     */
115    protected function validate_themes() {
116        if ( empty( $this->themes ) || ! is_array( $this->themes ) ) {
117            return new WP_Error( 'missing_themes', __( 'No themes found.', 'jetpack' ) );
118        }
119        foreach ( $this->themes as $theme ) {
120
121            if ( self::is_installed_theme( $theme ) ) {
122                return new WP_Error( 'theme_already_installed', __( 'The theme is already installed', 'jetpack' ) );
123            }
124
125            /**
126             * Filters whether to skip the standard method of downloading and validating a WordPress.com
127             * theme. An alternative method of WPCom theme download and validation can be
128             * executed during the filter.
129             *
130             * The filter can also return an instance of WP_Error; in which case the endpoint response will
131             * contain this error.
132             *
133             * @module json-api
134             *
135             * @since 4.4.2
136             *
137             * @param bool   $skip_download_filter_result Whether to skip the standard method of downloading
138             *                                            and validating a WPCom theme.
139             * @param string $theme_slug                  Theme name (slug). If it is a WPCom theme,
140             *                                            it should be suffixed with `-wpcom`.
141             */
142            $skip_download_filter_result = apply_filters( 'jetpack_wpcom_theme_skip_download', false, $theme );
143
144            if ( is_wp_error( $skip_download_filter_result ) ) {
145                return $skip_download_filter_result;
146            } elseif ( $skip_download_filter_result ) {
147                continue;
148            }
149
150            if ( wp_endswith( $theme, '-wpcom' ) ) {
151                $file = self::download_wpcom_theme_to_file( $theme );
152
153                if ( is_wp_error( $file ) ) {
154                    return $file;
155                }
156
157                $this->download_links[ $theme ] = $file;
158                continue;
159            }
160
161            $params     = (object) array( 'slug' => $theme );
162            $url        = 'https://api.wordpress.org/themes/info/1.0/'; // @todo Switch to https://api.wordpress.org/themes/info/1.1/, which uses JSON rather than PHP serialization.
163            $args       = array(
164                'body' => array(
165                    'action'  => 'theme_information',
166                    'request' => serialize( $params ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
167                ),
168            );
169            $response   = wp_remote_post( $url, $args );
170            $theme_data = unserialize( $response['body'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
171            if ( is_wp_error( $theme_data ) ) {
172                return $theme_data;
173            }
174
175            if ( ! is_object( $theme_data ) && ! isset( $theme_data->download_link ) ) {
176                return new WP_Error( 'theme_not_found', __( 'This theme does not exist', 'jetpack' ), 404 );
177            }
178
179            $this->download_links[ $theme ] = $theme_data->download_link;
180
181        }
182        return true;
183    }
184
185    /**
186     * Check if the theme is installed.
187     *
188     * @param string $theme - the theme we're checking.
189     *
190     * @return bool
191     */
192    protected static function is_installed_theme( $theme ) {
193        $wp_theme = wp_get_theme( $theme );
194        return $wp_theme->exists();
195    }
196
197    /**
198     * Download the wpcom theme.
199     *
200     * @param string $theme - the theme to download.
201     *
202     * @return string|WP_Error
203     */
204    protected static function download_wpcom_theme_to_file( $theme ) {
205
206        $file = wp_tempnam( 'theme' );
207        if ( ! $file ) {
208            return new WP_Error( 'problem_creating_theme_file', __( 'Problem creating file for theme download', 'jetpack' ) );
209        }
210
211        $url    = "themes/download/$theme.zip";
212        $args   = array(
213            'stream'   => true,
214            'filename' => $file,
215        );
216        $result = Client::wpcom_json_api_request_as_blog( $url, '1.1', $args );
217
218        $response = $result['response'];
219        if ( $response['code'] !== 200 ) {
220            wp_delete_file( $file );
221            return new WP_Error( 'problem_fetching_theme', __( 'Problem downloading theme', 'jetpack' ) );
222        }
223
224        return $file;
225    }
226}