PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.9.2
Jetpack – WP Security, Backup, Speed, & Growth v6.9.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 All 500 releases
jetpack / class.jetpack-xmlrpc-server.php
class.jetpack-xmlrpc-server.php
789 lines 24.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Just a sack of functions. Not actually an IXR_Server
5 */
6 class Jetpack_XMLRPC_Server {
7 /**
8 * The current error object
9 */
10 public $error = null;
11
12 /**
13 * The current user
14 */
15 public $user = null;
16
17 /**
18 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
19 * user is not authenticated (->login()) then the methods are never added,
20 * so they will get a "does not exist" error.
21 */
22 function xmlrpc_methods( $core_methods ) {
23 $jetpack_methods = array(
24 'jetpack.jsonAPI' => array( $this, 'json_api' ),
25 'jetpack.verifyAction' => array( $this, 'verify_action' ),
26 'jetpack.remoteRegister' => array( $this, 'remote_register' ),
27 'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
28 );
29
30 $this->user = $this->login();
31
32 if ( $this->user ) {
33 $jetpack_methods = array_merge( $jetpack_methods, array(
34 'jetpack.testConnection' => array( $this, 'test_connection' ),
35 'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),
36 'jetpack.featuresAvailable' => array( $this, 'features_available' ),
37 'jetpack.featuresEnabled' => array( $this, 'features_enabled' ),
38 'jetpack.disconnectBlog' => array( $this, 'disconnect_blog' ),
39 'jetpack.unlinkUser' => array( $this, 'unlink_user' ),
40 'jetpack.syncObject' => array( $this, 'sync_object' ),
41 'jetpack.idcUrlValidation' => array( $this, 'validate_urls_for_idc_mitigation' ),
42 ) );
43
44 if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
45 $jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
46 $jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
47 }
48
49 /**
50 * Filters the XML-RPC methods available to Jetpack for authenticated users.
51 *
52 * @since 1.1.0
53 *
54 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
55 * @param array $core_methods Available core XML-RPC methods.
56 * @param WP_User $user Information about a given WordPress user.
57 */
58 $jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
59 }
60
61 /**
62 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
63 *
64 * @since 3.0.0
65 *
66 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
67 * @param array $core_methods Available core XML-RPC methods.
68 */
69 return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
70 }
71
72 /**
73 * Whitelist of the bootstrap XML-RPC methods
74 */
75 function bootstrap_xmlrpc_methods() {
76 return array(
77 'jetpack.verifyRegistration' => array( $this, 'verify_registration' ),
78 'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
79 'jetpack.remoteRegister' => array( $this, 'remote_register' ),
80 );
81 }
82
83 function authorize_xmlrpc_methods() {
84 return array(
85 'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
86 );
87 }
88
89 function provision_xmlrpc_methods() {
90 return array(
91 'jetpack.remoteRegister' => array( $this, 'remote_register' ),
92 'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
93 'jetpack.remoteConnect' => array( $this, 'remote_connect' ),
94 );
95 }
96
97 function remote_authorize( $request ) {
98 $user = get_user_by( 'id', $request['state'] );
99 JetpackTracking::record_user_event( 'jpc_remote_authorize_begin', array(), $user );
100
101 foreach( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
102 if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
103 return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ), 'jpc_remote_authorize_fail' );
104 }
105 }
106
107 if ( ! $user ) {
108 return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'jpc_remote_authorize_fail' );
109 }
110
111 if ( Jetpack::is_active() && Jetpack::is_user_connected( $request['state'] ) ) {
112 return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'jpc_remote_authorize_fail' );
113 }
114
115 $verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
116
117 if ( is_a( $verified, 'IXR_Error' ) ) {
118 return $this->error( $verified, 'jpc_remote_authorize_fail' );
119 }
120
121 wp_set_current_user( $request['state'] );
122
123 $client_server = new Jetpack_Client_Server;
124 $result = $client_server->authorize( $request );
125
126 if ( is_wp_error( $result ) ) {
127 return $this->error( $result, 'jpc_remote_authorize_fail' );
128 }
129
130 JetpackTracking::record_user_event( 'jpc_remote_authorize_success' );
131
132 return array(
133 'result' => $result,
134 );
135 }
136
137 /**
138 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
139 * register this site so that a plan can be provisioned.
140 *
141 * @param array $request An array containing at minimum nonce and local_user keys.
142 *
143 * @return WP_Error|array
144 */
145 public function remote_register( $request ) {
146 JetpackTracking::record_user_event( 'jpc_remote_register_begin', array() );
147
148 $user = $this->fetch_and_verify_local_user( $request );
149
150 if ( ! $user ) {
151 return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_register_fail' );
152 }
153
154 if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
155 return $this->error( $user, 'jpc_remote_register_fail' );
156 }
157
158 if ( empty( $request['nonce'] ) ) {
159 return $this->error(
160 new Jetpack_Error(
161 'nonce_missing',
162 __( 'The required "nonce" parameter is missing.', 'jetpack' ),
163 400
164 ),
165 'jpc_remote_register_fail'
166 );
167 }
168
169 $nonce = sanitize_text_field( $request['nonce'] );
170 unset( $request['nonce'] );
171
172 $api_url = Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'partner_provision_nonce_check' ) );
173 $response = Jetpack_Client::_wp_remote_request(
174 esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
175 array( 'method' => 'GET' ),
176 true
177 );
178
179 if (
180 200 !== wp_remote_retrieve_response_code( $response ) ||
181 'OK' !== trim( wp_remote_retrieve_body( $response ) )
182 ) {
183 return $this->error(
184 new Jetpack_Error(
185 'invalid_nonce',
186 __( 'There was an issue validating this request.', 'jetpack' ),
187 400
188 ),
189 'jpc_remote_register_fail'
190 );
191 }
192
193 if ( ! Jetpack_Options::get_option( 'id' ) || ! Jetpack_Options::get_option( 'blog_token' ) || ! empty( $request['force'] ) ) {
194 wp_set_current_user( $user->ID );
195
196 // This code mostly copied from Jetpack::admin_page_load.
197 Jetpack::maybe_set_version_option();
198 $registered = Jetpack::try_registration();
199 if ( is_wp_error( $registered ) ) {
200 return $this->error( $registered, 'jpc_remote_register_fail' );
201 } elseif ( ! $registered ) {
202 return $this->error(
203 new Jetpack_Error(
204 'registration_error',
205 __( 'There was an unspecified error registering the site', 'jetpack' ),
206 400
207 ),
208 'jpc_remote_register_fail'
209 );
210 }
211 }
212
213 JetpackTracking::record_user_event( 'jpc_remote_register_success' );
214
215 return array(
216 'client_id' => Jetpack_Options::get_option( 'id' )
217 );
218 }
219
220 /**
221 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
222 * register this site so that a plan can be provisioned.
223 *
224 * @param array $request An array containing at minimum a nonce key and a local_username key.
225 *
226 * @return WP_Error|array
227 */
228 public function remote_provision( $request ) {
229 $user = $this->fetch_and_verify_local_user( $request );
230
231 if ( ! $user ) {
232 return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_provision_fail' );
233 }
234
235 if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
236 return $this->error( $user, 'jpc_remote_provision_fail' );
237 }
238
239 $site_icon = ( function_exists( 'has_site_icon' ) && has_site_icon() )
240 ? get_site_icon_url()
241 : false;
242
243 $auto_enable_sso = ( ! Jetpack::is_active() || Jetpack::is_module_active( 'sso' ) );
244
245 /** This filter is documented in class.jetpack-cli.php */
246 if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
247 $redirect_uri = add_query_arg(
248 array(
249 'action' => 'jetpack-sso',
250 'redirect_to' => rawurlencode( admin_url() ),
251 ),
252 wp_login_url() // TODO: come back to Jetpack dashboard?
253 );
254 } else {
255 $redirect_uri = admin_url();
256 }
257
258 // Generate secrets.
259 $role = Jetpack::translate_user_to_role( $user );
260 $secrets = Jetpack::init()->generate_secrets( 'authorize', $user->ID );
261
262 $response = array(
263 'jp_version' => JETPACK__VERSION,
264 'redirect_uri' => $redirect_uri,
265 'user_id' => $user->ID,
266 'user_email' => $user->user_email,
267 'user_login' => $user->user_login,
268 'scope' => Jetpack::sign_role( $role, $user->ID ),
269 'secret' => $secrets['secret_1'],
270 'is_active' => Jetpack::is_active(),
271 );
272
273 if ( $site_icon ) {
274 $response['site_icon'] = $site_icon;
275 }
276
277 if ( ! empty( $request['onboarding'] ) ) {
278 Jetpack::create_onboarding_token();
279 $response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
280 }
281
282 return $response;
283 }
284
285 /**
286 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
287 * an access token for the given user.
288 *
289 * @param array $request An array containing local_user and nonce keys at minimum.
290 * @return mixed
291 */
292 public function remote_connect( $request, $ixr_client = false ) {
293 if ( Jetpack::is_active() ) {
294 return $this->error(
295 new WP_Error(
296 'already_connected',
297 __( 'Jetpack is already connected.', 'jetpack' ),
298 400
299 ),
300 'jpc_remote_connect_fail'
301 );
302 }
303
304 $user = $this->fetch_and_verify_local_user( $request );
305
306 if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
307 return $this->error(
308 new WP_Error(
309 'input_error',
310 __( 'Valid user is required.', 'jetpack' ),
311 400
312 ),
313 'jpc_remote_connect_fail'
314 );
315 }
316
317 if ( empty( $request['nonce'] ) ) {
318 return $this->error(
319 new WP_Error(
320 'input_error',
321 __( 'A non-empty nonce must be supplied.', 'jetpack' ),
322 400
323 ),
324 'jpc_remote_connect_fail'
325 );
326 }
327
328 if ( ! $ixr_client ) {
329 Jetpack::load_xml_rpc_client();
330 $ixr_client = new Jetpack_IXR_Client();
331 }
332 $ixr_client->query( 'jetpack.getUserAccessToken', array(
333 'nonce' => sanitize_text_field( $request['nonce'] ),
334 'external_user_id' => $user->ID,
335 ) );
336
337 $token = $ixr_client->isError() ? false : $ixr_client->getResponse();
338 if ( empty( $token ) ) {
339 return $this->error(
340 new WP_Error(
341 'token_fetch_failed',
342 __( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
343 400
344 ),
345 'jpc_remote_connect_fail'
346 );
347 }
348 $token = sanitize_text_field( $token );
349
350 Jetpack::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
351
352 $this->do_post_authorization();
353
354 return Jetpack::is_active();
355 }
356
357 private function fetch_and_verify_local_user( $request ) {
358 if ( empty( $request['local_user'] ) ) {
359 return $this->error(
360 new Jetpack_Error(
361 'local_user_missing',
362 __( 'The required "local_user" parameter is missing.', 'jetpack' ),
363 400
364 ),
365 'jpc_remote_provision_fail'
366 );
367 }
368
369 // local user is used to look up by login, email or ID
370 $local_user_info = $request['local_user'];
371
372 $user = get_user_by( 'login', $local_user_info );
373
374 if ( ! $user ) {
375 $user = get_user_by( 'email', $local_user_info );
376 }
377
378 if ( ! $user ) {
379 $user = get_user_by( 'ID', $local_user_info );
380 }
381
382 return $user;
383 }
384
385 private function tracks_record_error( $name, $error, $user = null ) {
386 if ( is_wp_error( $error ) ) {
387 JetpackTracking::record_user_event( $name, array(
388 'error_code' => $error->get_error_code(),
389 'error_message' => $error->get_error_message()
390 ), $user );
391 } elseif( is_a( $error, 'IXR_Error' ) ) {
392 JetpackTracking::record_user_event( $name, array(
393 'error_code' => $error->code,
394 'error_message' => $error->message
395 ), $user );
396 }
397
398 return $error;
399 }
400
401 /**
402 * Verifies that Jetpack.WordPress.com received a registration request from this site
403 */
404 function verify_registration( $data ) {
405 // failure modes will be recorded in tracks in the verify_action method
406 return $this->verify_action( array( 'register', $data[0], $data[1] ) );
407 }
408
409 /**
410 * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
411 *
412 * Possible error_codes:
413 *
414 * verify_secret_1_missing
415 * verify_secret_1_malformed
416 * verify_secrets_missing: verification secrets are not found in database
417 * verify_secrets_incomplete: verification secrets are only partially found in database
418 * verify_secrets_expired: verification secrets have expired
419 * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
420 * state_missing: required parameter of state not found
421 * state_malformed: state is not a digit
422 * invalid_state: state in request does not match the stored state
423 *
424 * The 'authorize' and 'register' actions have additional error codes
425 *
426 * state_missing: a state ( user id ) was not supplied
427 * state_malformed: state is not the correct data type
428 * invalid_state: supplied state does not match the stored state
429 */
430 function verify_action( $params ) {
431 $action = $params[0];
432 $verify_secret = $params[1];
433 $state = isset( $params[2] ) ? $params[2] : '';
434 $user = get_user_by( 'id', $state );
435 JetpackTracking::record_user_event( 'jpc_verify_' . $action . '_begin', array(), $user );
436 $tracks_failure_event_name = 'jpc_verify_' . $action . '_fail';
437
438 if ( empty( $verify_secret ) ) {
439 return $this->error( new Jetpack_Error( 'verify_secret_1_missing', sprintf( 'The required "%s" parameter is missing.', 'secret_1' ), 400 ), $tracks_failure_event_name, $user );
440 } else if ( ! is_string( $verify_secret ) ) {
441 return $this->error( new Jetpack_Error( 'verify_secret_1_malformed', sprintf( 'The required "%s" parameter is malformed.', 'secret_1' ), 400 ), $tracks_failure_event_name, $user );
442 } else if ( empty( $state ) ) {
443 return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user );
444 } else if ( ! ctype_digit( $state ) ) {
445 return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user );
446 }
447
448 $secrets = Jetpack::get_secrets( $action, $state );
449
450 if ( ! $secrets ) {
451 Jetpack::delete_secrets( $action, $state );
452 return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user );
453 }
454
455 if ( is_wp_error( $secrets ) ) {
456 Jetpack::delete_secrets( $action, $state );
457 return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user );
458 }
459
460 if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) {
461 Jetpack::delete_secrets( $action, $state );
462 return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user );
463 }
464
465 if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) {
466 Jetpack::delete_secrets( $action, $state );
467 return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user );
468 }
469
470 Jetpack::delete_secrets( $action, $state );
471
472 JetpackTracking::record_user_event( 'jpc_verify_' . $action . '_success', array(), $user );
473
474 return $secrets['secret_2'];
475 }
476
477 /**
478 * Wrapper for wp_authenticate( $username, $password );
479 *
480 * @return WP_User|bool
481 */
482 function login() {
483 Jetpack::init()->require_jetpack_authentication();
484 $user = wp_authenticate( 'username', 'password' );
485 if ( is_wp_error( $user ) ) {
486 if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything.
487 $this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
488 } else {
489 $this->error = $user;
490 }
491 return false;
492 } else if ( !$user ) { // Shouldn't happen.
493 $this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
494 return false;
495 }
496
497 return $user;
498 }
499
500 /**
501 * Returns the current error as an IXR_Error
502 *
503 * @return bool|IXR_Error
504 */
505 function error( $error = null, $tracks_event_name = null, $user = null ) {
506 // record using Tracks
507 if ( null !== $tracks_event_name ) {
508 $this->tracks_record_error( $tracks_event_name, $error, $user );
509 }
510
511 if ( !is_null( $error ) ) {
512 $this->error = $error;
513 }
514
515 if ( is_wp_error( $this->error ) ) {
516 $code = $this->error->get_error_data();
517 if ( !$code ) {
518 $code = -10520;
519 }
520 $message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
521 return new IXR_Error( $code, $message );
522 } else if ( is_a( $this->error, 'IXR_Error' ) ) {
523 return $this->error;
524 }
525
526 return false;
527 }
528
529 /* API Methods */
530
531 /**
532 * Just authenticates with the given Jetpack credentials.
533 *
534 * @return string The current Jetpack version number
535 */
536 function test_connection() {
537 return JETPACK__VERSION;
538 }
539
540 function test_api_user_code( $args ) {
541 $client_id = (int) $args[0];
542 $user_id = (int) $args[1];
543 $nonce = (string) $args[2];
544 $verify = (string) $args[3];
545
546 if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
547 return false;
548 }
549
550 $user = get_user_by( 'id', $user_id );
551 if ( !$user || is_wp_error( $user ) ) {
552 return false;
553 }
554
555 /* debugging
556 error_log( "CLIENT: $client_id" );
557 error_log( "USER: $user_id" );
558 error_log( "NONCE: $nonce" );
559 error_log( "VERIFY: $verify" );
560 */
561
562 $jetpack_token = Jetpack_Data::get_access_token( $user_id );
563
564 $api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
565 if ( !$api_user_code ) {
566 return false;
567 }
568
569 $hmac = hash_hmac( 'md5', json_encode( (object) array(
570 'client_id' => (int) $client_id,
571 'user_id' => (int) $user_id,
572 'nonce' => (string) $nonce,
573 'code' => (string) $api_user_code,
574 ) ), $jetpack_token->secret );
575
576 if ( ! hash_equals( $hmac, $verify ) ) {
577 return false;
578 }
579
580 return $user_id;
581 }
582
583 /**
584 * Disconnect this blog from the connected wordpress.com account
585 * @return boolean
586 */
587 function disconnect_blog() {
588
589 // For tracking
590 if ( ! empty( $this->user->ID ) ) {
591 wp_set_current_user( $this->user->ID );
592 }
593
594 Jetpack::log( 'disconnect' );
595 Jetpack::disconnect();
596
597 return true;
598 }
599
600 /**
601 * Unlink a user from WordPress.com
602 *
603 * This will fail if called by the Master User.
604 */
605 function unlink_user() {
606 Jetpack::log( 'unlink' );
607 return Jetpack::unlink_user();
608 }
609
610 /**
611 * Returns any object that is able to be synced
612 */
613 function sync_object( $args ) {
614 // e.g. posts, post, 5
615 list( $module_name, $object_type, $id ) = $args;
616 require_once dirname( __FILE__ ) . '/sync/class.jetpack-sync-modules.php';
617 require_once dirname( __FILE__ ) . '/sync/class.jetpack-sync-sender.php';
618
619 $sync_module = Jetpack_Sync_Modules::get_module( $module_name );
620 $codec = Jetpack_Sync_Sender::get_instance()->get_codec();
621
622 return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
623 }
624
625 /**
626 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
627 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
628 * and the remote Jetpack site.
629 *
630 * @return array
631 */
632 function validate_urls_for_idc_mitigation() {
633 require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php';
634 return array(
635 'home' => Jetpack_Sync_Functions::home_url(),
636 'siteurl' => Jetpack_Sync_Functions::site_url(),
637 );
638 }
639
640 /**
641 * Returns what features are available. Uses the slug of the module files.
642 *
643 * @return array
644 */
645 function features_available() {
646 $raw_modules = Jetpack::get_available_modules();
647 $modules = array();
648 foreach ( $raw_modules as $module ) {
649 $modules[] = Jetpack::get_module_slug( $module );
650 }
651
652 return $modules;
653 }
654
655 /**
656 * Returns what features are enabled. Uses the slug of the modules files.
657 *
658 * @return array
659 */
660 function features_enabled() {
661 $raw_modules = Jetpack::get_active_modules();
662 $modules = array();
663 foreach ( $raw_modules as $module ) {
664 $modules[] = Jetpack::get_module_slug( $module );
665 }
666
667 return $modules;
668 }
669
670 function update_attachment_parent( $args ) {
671 $attachment_id = (int) $args[0];
672 $parent_id = (int) $args[1];
673
674 return wp_update_post( array(
675 'ID' => $attachment_id,
676 'post_parent' => $parent_id,
677 ) );
678 }
679
680 function json_api( $args = array() ) {
681 $json_api_args = $args[0];
682 $verify_api_user_args = $args[1];
683
684 $method = (string) $json_api_args[0];
685 $url = (string) $json_api_args[1];
686 $post_body = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
687 $user_details = (array) $json_api_args[4];
688 $locale = (string) $json_api_args[5];
689
690 if ( !$verify_api_user_args ) {
691 $user_id = 0;
692 } elseif ( 'internal' === $verify_api_user_args[0] ) {
693 $user_id = (int) $verify_api_user_args[1];
694 if ( $user_id ) {
695 $user = get_user_by( 'id', $user_id );
696 if ( !$user || is_wp_error( $user ) ) {
697 return false;
698 }
699 }
700 } else {
701 $user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
702 if ( !$user_id ) {
703 return false;
704 }
705 }
706
707 /* debugging
708 error_log( "-- begin json api via jetpack debugging -- " );
709 error_log( "METHOD: $method" );
710 error_log( "URL: $url" );
711 error_log( "POST BODY: $post_body" );
712 error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
713 error_log( "VERIFIED USER_ID: " . (int) $user_id );
714 error_log( "-- end json api via jetpack debugging -- " );
715 */
716
717 if ( 'en' !== $locale ) {
718 // .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
719 $new_locale = $locale;
720 if ( strpos( $locale, '-' ) !== false ) {
721 $locale_pieces = explode( '-', $locale );
722 $new_locale = $locale_pieces[0];
723 $new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
724 } else {
725 // .com might pass 'fr' because thats what our language files are named as, where core seems
726 // to do fr_FR - so try that if we don't think we can load the file.
727 if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
728 $new_locale = $locale . '_' . strtoupper( $locale );
729 }
730 }
731
732 if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
733 unload_textdomain( 'default' );
734 load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
735 }
736 }
737
738 $old_user = wp_get_current_user();
739 wp_set_current_user( $user_id );
740
741 $token = Jetpack_Data::get_access_token( get_current_user_id() );
742 if ( !$token || is_wp_error( $token ) ) {
743 return false;
744 }
745
746 define( 'REST_API_REQUEST', true );
747 define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
748
749 // needed?
750 require_once ABSPATH . 'wp-admin/includes/admin.php';
751
752 require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
753 $api = WPCOM_JSON_API::init( $method, $url, $post_body );
754 $api->token_details['user'] = $user_details;
755 require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
756
757 $display_errors = ini_set( 'display_errors', 0 );
758 ob_start();
759 $content_type = $api->serve( false );
760 $output = ob_get_clean();
761 ini_set( 'display_errors', $display_errors );
762
763 $nonce = wp_generate_password( 10, false );
764 $hmac = hash_hmac( 'md5', $nonce . $output, $token->secret );
765
766 wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
767
768 return array(
769 (string) $output,
770 (string) $nonce,
771 (string) $hmac,
772 );
773 }
774
775 /**
776 * Handles authorization actions after connecting a site, such as enabling modules.
777 *
778 * This do_post_authorization() is used in this class, as opposed to calling
779 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
780 *
781 * @return void
782 */
783 public function do_post_authorization() {
784 /** This filter is documented in class.jetpack-cli.php */
785 $enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
786 Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
787 }
788 }
789