1 | <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName |
---|
2 | /** |
---|
3 | * Class file for provisioning Jetpack. |
---|
4 | * |
---|
5 | * @package automattic/jetpack |
---|
6 | */ |
---|
7 | |
---|
8 | use Automattic\Jetpack\Connection\Client; |
---|
9 | use Automattic\Jetpack\Connection\Secrets; |
---|
10 | use Automattic\Jetpack\Connection\Tokens; |
---|
11 | use Automattic\Jetpack\Identity_Crisis; |
---|
12 | use Automattic\Jetpack\Roles; |
---|
13 | use Automattic\Jetpack\Sync\Actions; |
---|
14 | |
---|
15 | /** |
---|
16 | * Jetpack_Provision class. |
---|
17 | */ |
---|
18 | class Jetpack_Provision { |
---|
19 | |
---|
20 | /** |
---|
21 | * Responsible for checking pre-conditions, registering site, and returning an array of details |
---|
22 | * that can be used to provision a plan for the site. |
---|
23 | * |
---|
24 | * @param array $named_args The array of arguments. |
---|
25 | * |
---|
26 | * @return WP_Error|array |
---|
27 | */ |
---|
28 | public static function register_and_build_request_body( $named_args ) { |
---|
29 | $url_args = array( |
---|
30 | 'home_url' => 'WP_HOME', |
---|
31 | 'site_url' => 'WP_SITEURL', |
---|
32 | ); |
---|
33 | |
---|
34 | foreach ( $url_args as $url_arg => $constant_name ) { |
---|
35 | if ( isset( $named_args[ $url_arg ] ) ) { |
---|
36 | add_filter( |
---|
37 | $url_arg, |
---|
38 | function () use ( $url_arg, $named_args ) { |
---|
39 | return $named_args[ $url_arg ]; |
---|
40 | }, |
---|
41 | 11 |
---|
42 | ); |
---|
43 | } |
---|
44 | } |
---|
45 | |
---|
46 | // If Jetpack is currently connected, and is not in Safe Mode already, kick off a sync of the current |
---|
47 | // functions/callables so that we can test if this site is in IDC. |
---|
48 | if ( Jetpack::is_connection_ready() && ! Identity_Crisis::validate_sync_error_idc_option() && Actions::sync_allowed() ) { |
---|
49 | Actions::do_full_sync( array( 'functions' => true ) ); |
---|
50 | Actions::$sender->do_full_sync(); |
---|
51 | } |
---|
52 | |
---|
53 | if ( Identity_Crisis::validate_sync_error_idc_option() ) { |
---|
54 | return new WP_Error( |
---|
55 | 'site_in_safe_mode', |
---|
56 | __( 'Can not provision a plan while in safe mode. See: https://jetpack.com/support/safe-mode/', 'jetpack' ) |
---|
57 | ); |
---|
58 | } |
---|
59 | |
---|
60 | if ( ! Jetpack::connection()->is_connected() || ( isset( $named_args['force_register'] ) && (int) $named_args['force_register'] ) ) { |
---|
61 | // This code mostly copied from Jetpack::admin_page_load. |
---|
62 | Jetpack::maybe_set_version_option(); |
---|
63 | Jetpack::connection()->add_register_request_param( 'from', 'jetpack-start' ); |
---|
64 | $registered = Jetpack::connection()->try_registration(); |
---|
65 | if ( is_wp_error( $registered ) ) { |
---|
66 | return $registered; |
---|
67 | } elseif ( ! $registered ) { |
---|
68 | return new WP_Error( 'registration_error', __( 'There was an unspecified error registering the site', 'jetpack' ) ); |
---|
69 | } |
---|
70 | } |
---|
71 | |
---|
72 | // If the user isn't specified, but we have a current master user, then set that to current user. |
---|
73 | $master_user_id = Jetpack_Options::get_option( 'master_user' ); |
---|
74 | if ( ! get_current_user_id() && $master_user_id ) { |
---|
75 | wp_set_current_user( $master_user_id ); |
---|
76 | } |
---|
77 | |
---|
78 | $site_icon = get_site_icon_url(); |
---|
79 | |
---|
80 | $auto_enable_sso = ( ! Jetpack::connection()->has_connected_owner() || Jetpack::is_module_active( 'sso' ) ); |
---|
81 | |
---|
82 | /** This filter is documented in class.jetpack-cli.php */ |
---|
83 | if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) { |
---|
84 | $redirect_uri = add_query_arg( |
---|
85 | array( |
---|
86 | 'action' => 'jetpack-sso', |
---|
87 | 'redirect_to' => rawurlencode( admin_url() ), |
---|
88 | ), |
---|
89 | wp_login_url() // TODO: come back to Jetpack dashboard? |
---|
90 | ); |
---|
91 | } else { |
---|
92 | $redirect_uri = admin_url(); |
---|
93 | } |
---|
94 | |
---|
95 | $request_body = array( |
---|
96 | 'jp_version' => JETPACK__VERSION, |
---|
97 | 'redirect_uri' => $redirect_uri, |
---|
98 | ); |
---|
99 | |
---|
100 | if ( $site_icon ) { |
---|
101 | $request_body['site_icon'] = $site_icon; |
---|
102 | } |
---|
103 | |
---|
104 | if ( get_current_user_id() ) { |
---|
105 | $user = wp_get_current_user(); |
---|
106 | |
---|
107 | // Role. |
---|
108 | $roles = new Roles(); |
---|
109 | $role = $roles->translate_current_user_to_role(); |
---|
110 | $signed_role = Jetpack::connection()->sign_role( $role ); |
---|
111 | |
---|
112 | $secrets = ( new Secrets() )->generate( 'authorize' ); |
---|
113 | |
---|
114 | // Jetpack auth stuff. |
---|
115 | $request_body['scope'] = $signed_role; |
---|
116 | $request_body['secret'] = $secrets['secret_1']; |
---|
117 | |
---|
118 | // User stuff. |
---|
119 | $request_body['user_id'] = $user->ID; |
---|
120 | $request_body['user_email'] = $user->user_email; |
---|
121 | $request_body['user_login'] = $user->user_login; |
---|
122 | } |
---|
123 | |
---|
124 | // Optional additional params. |
---|
125 | if ( isset( $named_args['wpcom_user_id'] ) && ! empty( $named_args['wpcom_user_id'] ) ) { |
---|
126 | $request_body['wpcom_user_id'] = $named_args['wpcom_user_id']; |
---|
127 | } |
---|
128 | |
---|
129 | // Override email of selected user. |
---|
130 | if ( isset( $named_args['wpcom_user_email'] ) && ! empty( $named_args['wpcom_user_email'] ) ) { |
---|
131 | $request_body['user_email'] = $named_args['wpcom_user_email']; |
---|
132 | } |
---|
133 | |
---|
134 | if ( isset( $named_args['plan'] ) && ! empty( $named_args['plan'] ) ) { |
---|
135 | $request_body['plan'] = $named_args['plan']; |
---|
136 | } |
---|
137 | |
---|
138 | if ( isset( $named_args['onboarding'] ) && ! empty( $named_args['onboarding'] ) ) { |
---|
139 | $request_body['onboarding'] = (int) $named_args['onboarding']; |
---|
140 | } |
---|
141 | |
---|
142 | if ( isset( $named_args['force_connect'] ) && ! empty( $named_args['force_connect'] ) ) { |
---|
143 | $request_body['force_connect'] = (int) $named_args['force_connect']; |
---|
144 | } |
---|
145 | |
---|
146 | if ( isset( $request_body['onboarding'] ) && (bool) $request_body['onboarding'] ) { |
---|
147 | Jetpack::create_onboarding_token(); |
---|
148 | } |
---|
149 | |
---|
150 | return $request_body; |
---|
151 | } |
---|
152 | |
---|
153 | /** |
---|
154 | * Given an access token and an array of arguments, will provision a plan for this site. |
---|
155 | * |
---|
156 | * @param string $access_token The access token from the partner. |
---|
157 | * @param array $named_args The arguments used for registering the site and then provisioning a plan. |
---|
158 | * |
---|
159 | * @return WP_Error|array |
---|
160 | */ |
---|
161 | public static function partner_provision( $access_token, $named_args ) { |
---|
162 | // First, verify the token. |
---|
163 | $verify_response = self::verify_token( $access_token ); |
---|
164 | |
---|
165 | if ( is_wp_error( $verify_response ) ) { |
---|
166 | return $verify_response; |
---|
167 | } |
---|
168 | |
---|
169 | $request_body = self::register_and_build_request_body( $named_args ); |
---|
170 | if ( is_wp_error( $request_body ) ) { |
---|
171 | return $request_body; |
---|
172 | } |
---|
173 | |
---|
174 | $request = array( |
---|
175 | 'headers' => array( |
---|
176 | 'Authorization' => "Bearer $access_token", |
---|
177 | 'Host' => 'public-api.wordpress.com', |
---|
178 | ), |
---|
179 | 'timeout' => 60, |
---|
180 | 'method' => 'POST', |
---|
181 | 'body' => wp_json_encode( $request_body ), |
---|
182 | ); |
---|
183 | |
---|
184 | $blog_id = Jetpack_Options::get_option( 'id' ); |
---|
185 | $url = esc_url_raw( |
---|
186 | sprintf( |
---|
187 | '%s/rest/v1.3/jpphp/%d/partner-provision', |
---|
188 | self::get_api_host(), |
---|
189 | $blog_id |
---|
190 | ) |
---|
191 | ); |
---|
192 | if ( ! empty( $named_args['partner_tracking_id'] ) ) { |
---|
193 | $url = esc_url_raw( add_query_arg( 'partner_tracking_id', $named_args['partner_tracking_id'], $url ) ); |
---|
194 | } |
---|
195 | |
---|
196 | // Add calypso env if set. |
---|
197 | $calypso_env = ( new \Automattic\Jetpack\Status\Host() )->get_calypso_env(); |
---|
198 | if ( ! empty( $calypso_env ) ) { |
---|
199 | $url = add_query_arg( array( 'calypso_env' => $calypso_env ), $url ); |
---|
200 | } |
---|
201 | |
---|
202 | $result = Client::_wp_remote_request( $url, $request ); |
---|
203 | |
---|
204 | if ( is_wp_error( $result ) ) { |
---|
205 | return $result; |
---|
206 | } |
---|
207 | |
---|
208 | $response_code = wp_remote_retrieve_response_code( $result ); |
---|
209 | $body_json = json_decode( wp_remote_retrieve_body( $result ) ); |
---|
210 | |
---|
211 | if ( 200 !== $response_code ) { |
---|
212 | if ( isset( $body_json->error ) ) { |
---|
213 | return new WP_Error( $body_json->error, $body_json->message ); |
---|
214 | } else { |
---|
215 | return new WP_Error( |
---|
216 | 'server_error', |
---|
217 | /* translators: %s is an HTTP status code retured from an API request. Ex. – 400 */ |
---|
218 | sprintf( __( 'Request failed with code %s', 'jetpack' ), $response_code ) |
---|
219 | ); |
---|
220 | } |
---|
221 | } |
---|
222 | |
---|
223 | if ( isset( $body_json->access_token ) && is_user_logged_in() ) { |
---|
224 | // Check if this matches the existing token before replacing. |
---|
225 | $existing_token = ( new Tokens() )->get_access_token( get_current_user_id() ); |
---|
226 | if ( empty( $existing_token ) || $existing_token->secret !== $body_json->access_token ) { |
---|
227 | self::authorize_user( get_current_user_id(), $body_json->access_token ); |
---|
228 | } |
---|
229 | } |
---|
230 | |
---|
231 | return $body_json; |
---|
232 | } |
---|
233 | |
---|
234 | /** |
---|
235 | * Authorizes the passed user. |
---|
236 | * |
---|
237 | * @param int $user_id User ID. |
---|
238 | * @param string $access_token Access token. |
---|
239 | */ |
---|
240 | private static function authorize_user( $user_id, $access_token ) { |
---|
241 | // authorize user and enable SSO. |
---|
242 | ( new Tokens() )->update_user_token( $user_id, sprintf( '%s.%d', $access_token, $user_id ), true ); |
---|
243 | |
---|
244 | /** |
---|
245 | * Auto-enable SSO module for new Jetpack Start connections |
---|
246 | * |
---|
247 | * @since 5.0.0 |
---|
248 | * |
---|
249 | * @param bool $enable_sso Whether to enable the SSO module. Default to true. |
---|
250 | */ |
---|
251 | $other_modules = apply_filters( 'jetpack_start_enable_sso', true ) |
---|
252 | ? array( 'sso' ) |
---|
253 | : array(); |
---|
254 | |
---|
255 | $active_modules = Jetpack_Options::get_option( 'active_modules' ); |
---|
256 | |
---|
257 | if ( $active_modules ) { |
---|
258 | Jetpack::delete_active_modules(); |
---|
259 | Jetpack::activate_default_modules( 999, 1, array_merge( $active_modules, $other_modules ), false ); |
---|
260 | } else { |
---|
261 | Jetpack::activate_default_modules( false, false, $other_modules, false ); |
---|
262 | } |
---|
263 | } |
---|
264 | |
---|
265 | /** |
---|
266 | * Verifies the access token being used. |
---|
267 | * |
---|
268 | * @param string $access_token Access token. |
---|
269 | * |
---|
270 | * @return array|\Automattic\Jetpack\Connection\WP_Error|bool|WP_Error |
---|
271 | */ |
---|
272 | private static function verify_token( $access_token ) { |
---|
273 | $request = array( |
---|
274 | 'headers' => array( |
---|
275 | 'Authorization' => 'Bearer ' . $access_token, |
---|
276 | 'Host' => 'public-api.wordpress.com', |
---|
277 | ), |
---|
278 | 'timeout' => 10, |
---|
279 | 'method' => 'POST', |
---|
280 | 'body' => '', |
---|
281 | ); |
---|
282 | |
---|
283 | $url = sprintf( '%s/rest/v1.3/jpphp/partner-keys/verify', self::get_api_host() ); |
---|
284 | $result = Client::_wp_remote_request( $url, $request ); |
---|
285 | |
---|
286 | if ( is_wp_error( $result ) ) { |
---|
287 | return $result; |
---|
288 | } |
---|
289 | |
---|
290 | $response_code = wp_remote_retrieve_response_code( $result ); |
---|
291 | $body_json = json_decode( wp_remote_retrieve_body( $result ) ); |
---|
292 | |
---|
293 | if ( 200 !== $response_code ) { |
---|
294 | if ( isset( $body_json->error ) ) { |
---|
295 | return new WP_Error( $body_json->error, $body_json->message ); |
---|
296 | } else { |
---|
297 | /* translators: %s is HTTP response code (e.g. 500, 401, etc). */ |
---|
298 | return new WP_Error( 'server_error', sprintf( __( 'Request failed with code %s', 'jetpack' ), $response_code ) ); |
---|
299 | } |
---|
300 | } |
---|
301 | |
---|
302 | return true; |
---|
303 | } |
---|
304 | |
---|
305 | /** |
---|
306 | * Gets the API host as set via env. |
---|
307 | * |
---|
308 | * @return string API URL. |
---|
309 | */ |
---|
310 | private static function get_api_host() { |
---|
311 | $env_api_host = getenv( 'JETPACK_START_API_HOST', true ); |
---|
312 | return $env_api_host ? 'https://' . $env_api_host : JETPACK__WPCOM_JSON_API_BASE; |
---|
313 | } |
---|
314 | } |
---|