Completed
Push — update/jpc-event-names-to-be-l... ( d1adc0...021130 )
by
unknown
186:25 queued 178:00
created

class.jetpack-xmlrpc-server.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 = get_site_icon_url();
240
241
		$auto_enable_sso = ( ! Jetpack::is_active() || Jetpack::is_module_active( 'sso' ) );
242
243
		/** This filter is documented in class.jetpack-cli.php */
244
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
245
			$redirect_uri = add_query_arg(
246
				array(
247
					'action'      => 'jetpack-sso',
248
					'redirect_to' => rawurlencode( admin_url() ),
249
				),
250
				wp_login_url() // TODO: come back to Jetpack dashboard?
251
			);
252
		} else {
253
			$redirect_uri = admin_url();
254
		}
255
256
		// Generate secrets.
257
		$role    = Jetpack::translate_user_to_role( $user );
258
		$secrets = Jetpack::init()->generate_secrets( 'authorize', $user->ID );
259
260
		$response = array(
261
			'jp_version'   => JETPACK__VERSION,
262
			'redirect_uri' => $redirect_uri,
263
			'user_id'      => $user->ID,
264
			'user_email'   => $user->user_email,
265
			'user_login'   => $user->user_login,
266
			'scope'        => Jetpack::sign_role( $role, $user->ID ),
267
			'secret'       => $secrets['secret_1'],
268
			'is_active'    => Jetpack::is_active(),
269
		);
270
271
		if ( $site_icon ) {
272
			$response['site_icon'] = $site_icon;
273
		}
274
275
		if ( ! empty( $request['onboarding'] ) ) {
276
			Jetpack::create_onboarding_token();
277
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
278
		}
279
280
		return $response;
281
	}
282
283
	/**
284
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
285
	 * an access token for the given user.
286
	 *
287
	 * @param array $request An array containing local_user and nonce keys at minimum.
288
	 * @return mixed
289
	 */
290
	public function remote_connect( $request, $ixr_client = false ) {
291
		if ( Jetpack::is_active() ) {
292
			return $this->error(
293
				new WP_Error(
294
					'already_connected',
295
					__( 'Jetpack is already connected.', 'jetpack' ),
296
					400
297
				),
298
				'jpc_remote_connect_fail'
299
			);
300
		}
301
302
		$user = $this->fetch_and_verify_local_user( $request );
303
304
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
305
			return $this->error(
306
				new WP_Error(
307
					'input_error',
308
					__( 'Valid user is required.', 'jetpack' ),
309
					400
310
				),
311
				'jpc_remote_connect_fail'
312
			);
313
		}
314
315
		if ( empty( $request['nonce'] ) ) {
316
			return $this->error(
317
				new WP_Error(
318
					'input_error',
319
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
320
					400
321
				),
322
				'jpc_remote_connect_fail'
323
			);
324
		}
325
326
		if ( ! $ixr_client ) {
327
			Jetpack::load_xml_rpc_client();
328
			$ixr_client = new Jetpack_IXR_Client();
329
		}
330
		$ixr_client->query( 'jetpack.getUserAccessToken', array(
331
			'nonce'            => sanitize_text_field( $request['nonce'] ),
332
			'external_user_id' => $user->ID,
333
		) );
334
335
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
336
		if ( empty( $token ) ) {
337
			return $this->error(
338
				new WP_Error(
339
					'token_fetch_failed',
340
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
341
					400
342
				),
343
				'jpc_remote_connect_fail'
344
			);
345
		}
346
		$token = sanitize_text_field( $token );
347
348
		Jetpack::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
349
350
		$this->do_post_authorization();
351
352
		return Jetpack::is_active();
353
	}
354
355
	private function fetch_and_verify_local_user( $request ) {
356
		if ( empty( $request['local_user'] ) ) {
357
			return $this->error(
358
				new Jetpack_Error(
359
					'local_user_missing',
360
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
361
					400
362
				),
363
				'jpc_remote_provision_fail'
364
			);
365
		}
366
367
		// local user is used to look up by login, email or ID
368
		$local_user_info = $request['local_user'];
369
370
		$user = get_user_by( 'login', $local_user_info );
371
372
		if ( ! $user ) {
373
			$user = get_user_by( 'email', $local_user_info );
374
		}
375
376
		if ( ! $user ) {
377
			$user = get_user_by( 'ID', $local_user_info );
378
		}
379
380
		return $user;
381
	}
382
383
	private function tracks_record_error( $name, $error, $user = null ) {
384
		if ( is_wp_error( $error ) ) {
385
			JetpackTracking::record_user_event( $name, array(
386
				'error_code' => $error->get_error_code(),
387
				'error_message' => $error->get_error_message()
388
			), $user );
389
		} elseif( is_a( $error, 'IXR_Error' ) ) {
390
			JetpackTracking::record_user_event( $name, array(
391
				'error_code' => $error->code,
392
				'error_message' => $error->message
393
			), $user );
394
		}
395
396
		return $error;
397
	}
398
399
	/**
400
	* Verifies that Jetpack.WordPress.com received a registration request from this site
401
	*/
402
	function verify_registration( $data ) {
403
		// failure modes will be recorded in tracks in the verify_action method
404
		return $this->verify_action( array( 'register', $data[0], $data[1] ) );
405
	}
406
407
	/**
408
	 * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
409
	 *
410
	 * Possible error_codes:
411
	 *
412
	 * verify_secret_1_missing
413
	 * verify_secret_1_malformed
414
	 * verify_secrets_missing: verification secrets are not found in database
415
	 * verify_secrets_incomplete: verification secrets are only partially found in database
416
	 * verify_secrets_expired: verification secrets have expired
417
	 * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
418
	 * state_missing: required parameter of state not found
419
	 * state_malformed: state is not a digit
420
	 * invalid_state: state in request does not match the stored state
421
	 *
422
	 * The 'authorize' and 'register' actions have additional error codes
423
	 *
424
	 * state_missing: a state ( user id ) was not supplied
425
	 * state_malformed: state is not the correct data type
426
	 * invalid_state: supplied state does not match the stored state
427
	 */
428
	function verify_action( $params ) {
429
		$action = $params[0];
430
		$verify_secret = $params[1];
431
		$state = isset( $params[2] ) ? $params[2] : '';
432
		$user = get_user_by( 'id', $state );
433
		$tracks_failure_event_name = '';
434
435
		if ( 'authorize' === $action ) {
436
			$tracks_failure_event_name = 'jpc_verify_authorize_fail';
437
			JetpackTracking::record_user_event( 'jpc_verify_authorize_begin', array(), $user );
438
		}
439
		if ( 'register' === $action ) {
440
			$tracks_failure_event_name 'jpc_verify_register_fail';
0 ignored issues
show
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_CONSTANT_ENCAPSED_STRING
Loading history...
441
			JetpackTracking::record_user_event( 'jpc_verify_register_begin', array(), $user );
442
		}
443
444
		if ( empty( $verify_secret ) ) {
445
			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 );
446
		} else if ( ! is_string( $verify_secret ) ) {
447
			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 );
448
		} else if ( empty( $state ) ) {
449
			return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user );
450
		} else if ( ! ctype_digit( $state ) ) {
451
			return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user );
452
		}
453
454
		$secrets = Jetpack::get_secrets( $action, $state );
455
456
		if ( ! $secrets ) {
457
			Jetpack::delete_secrets( $action, $state );
458
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user );
459
		}
460
461
		if ( is_wp_error( $secrets ) ) {
462
			Jetpack::delete_secrets( $action, $state );
463
			return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user );
464
		}
465
466
		if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) {
467
			Jetpack::delete_secrets( $action, $state );
468
			return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user );
469
		}
470
471
		if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) {
472
			Jetpack::delete_secrets( $action, $state );
473
			return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user );
474
		}
475
476
		Jetpack::delete_secrets( $action, $state );
477
478
		if ( 'authorize' === $action ) {
479
			JetpackTracking::record_user_event( 'jpc_verify_authorize_success', array(), $user );
480
		}
481
		if ( 'register' === $action ) {
482
			JetpackTracking::record_user_event( 'jpc_verify_register_success', array(), $user );
483
		}
484
485
		return $secrets['secret_2'];
486
	}
487
488
	/**
489
	 * Wrapper for wp_authenticate( $username, $password );
490
	 *
491
	 * @return WP_User|bool
492
	 */
493
	function login() {
494
		Jetpack::init()->require_jetpack_authentication();
495
		$user = wp_authenticate( 'username', 'password' );
496
		if ( is_wp_error( $user ) ) {
497
			if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything.
498
				$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
499
			} else {
500
				$this->error = $user;
501
			}
502
			return false;
503
		} else if ( !$user ) { // Shouldn't happen.
504
			$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
505
			return false;
506
		}
507
508
		return $user;
509
	}
510
511
	/**
512
	 * Returns the current error as an IXR_Error
513
	 *
514
	 * @return bool|IXR_Error
515
	 */
516
	function error( $error = null, $tracks_event_name = null, $user = null ) {
517
		// record using Tracks
518
		if ( null !== $tracks_event_name ) {
519
			$this->tracks_record_error( $tracks_event_name, $error, $user );
520
		}
521
522
		if ( !is_null( $error ) ) {
523
			$this->error = $error;
524
		}
525
526
		if ( is_wp_error( $this->error ) ) {
527
			$code = $this->error->get_error_data();
528
			if ( !$code ) {
529
				$code = -10520;
530
			}
531
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
532
			return new IXR_Error( $code, $message );
533
		} else if ( is_a( $this->error, 'IXR_Error' ) ) {
534
			return $this->error;
535
		}
536
537
		return false;
538
	}
539
540
/* API Methods */
541
542
	/**
543
	 * Just authenticates with the given Jetpack credentials.
544
	 *
545
	 * @return string The current Jetpack version number
546
	 */
547
	function test_connection() {
548
		return JETPACK__VERSION;
549
	}
550
551
	function test_api_user_code( $args ) {
552
		$client_id = (int) $args[0];
553
		$user_id   = (int) $args[1];
554
		$nonce     = (string) $args[2];
555
		$verify    = (string) $args[3];
556
557
		if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
558
			return false;
559
		}
560
561
		$user = get_user_by( 'id', $user_id );
562
		if ( !$user || is_wp_error( $user ) ) {
563
			return false;
564
		}
565
566
		/* debugging
567
		error_log( "CLIENT: $client_id" );
568
		error_log( "USER:   $user_id" );
569
		error_log( "NONCE:  $nonce" );
570
		error_log( "VERIFY: $verify" );
571
		*/
572
573
		$jetpack_token = Jetpack_Data::get_access_token( $user_id );
574
575
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
576
		if ( !$api_user_code ) {
577
			return false;
578
		}
579
580
		$hmac = hash_hmac( 'md5', json_encode( (object) array(
581
			'client_id' => (int) $client_id,
582
			'user_id'   => (int) $user_id,
583
			'nonce'     => (string) $nonce,
584
			'code'      => (string) $api_user_code,
585
		) ), $jetpack_token->secret );
586
587
		if ( ! hash_equals( $hmac, $verify ) ) {
588
			return false;
589
		}
590
591
		return $user_id;
592
	}
593
594
	/**
595
	* Disconnect this blog from the connected wordpress.com account
596
	* @return boolean
597
	*/
598
	function disconnect_blog() {
599
600
		// For tracking
601
		if ( ! empty( $this->user->ID ) ) {
602
			wp_set_current_user( $this->user->ID );
603
		}
604
605
		Jetpack::log( 'disconnect' );
606
		Jetpack::disconnect();
607
608
		return true;
609
	}
610
611
	/**
612
	 * Unlink a user from WordPress.com
613
	 *
614
	 * This will fail if called by the Master User.
615
	 */
616
	function unlink_user() {
617
		Jetpack::log( 'unlink' );
618
		return Jetpack::unlink_user();
619
	}
620
621
	/**
622
	 * Returns any object that is able to be synced
623
	 */
624
	function sync_object( $args ) {
625
		// e.g. posts, post, 5
626
		list( $module_name, $object_type, $id ) = $args;
627
		require_once dirname( __FILE__ ) . '/sync/class.jetpack-sync-modules.php';
628
		require_once dirname( __FILE__ ) . '/sync/class.jetpack-sync-sender.php';
629
630
		$sync_module = Jetpack_Sync_Modules::get_module( $module_name );
631
		$codec = Jetpack_Sync_Sender::get_instance()->get_codec();
632
633
		return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
634
	}
635
636
	/**
637
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
638
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
639
	 * and the remote Jetpack site.
640
	 *
641
	 * @return array
642
	 */
643
	function validate_urls_for_idc_mitigation() {
644
		require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php';
645
		return array(
646
			'home'    => Jetpack_Sync_Functions::home_url(),
647
			'siteurl' => Jetpack_Sync_Functions::site_url(),
648
		);
649
	}
650
651
	/**
652
	 * Returns what features are available. Uses the slug of the module files.
653
	 *
654
	 * @return array
655
	 */
656
	function features_available() {
657
		$raw_modules = Jetpack::get_available_modules();
658
		$modules = array();
659
		foreach ( $raw_modules as $module ) {
660
			$modules[] = Jetpack::get_module_slug( $module );
661
		}
662
663
		return $modules;
664
	}
665
666
	/**
667
	 * Returns what features are enabled. Uses the slug of the modules files.
668
	 *
669
	 * @return array
670
	 */
671
	function features_enabled() {
672
		$raw_modules = Jetpack::get_active_modules();
673
		$modules = array();
674
		foreach ( $raw_modules as $module ) {
675
			$modules[] = Jetpack::get_module_slug( $module );
676
		}
677
678
		return $modules;
679
	}
680
681
	function update_attachment_parent( $args ) {
682
		$attachment_id = (int) $args[0];
683
		$parent_id     = (int) $args[1];
684
685
		return wp_update_post( array(
686
			'ID'          => $attachment_id,
687
			'post_parent' => $parent_id,
688
		) );
689
	}
690
691
	function json_api( $args = array() ) {
692
		$json_api_args = $args[0];
693
		$verify_api_user_args = $args[1];
694
695
		$method       = (string) $json_api_args[0];
696
		$url          = (string) $json_api_args[1];
697
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
698
		$user_details = (array) $json_api_args[4];
699
		$locale       = (string) $json_api_args[5];
700
701
		if ( !$verify_api_user_args ) {
702
			$user_id = 0;
703
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
704
			$user_id = (int) $verify_api_user_args[1];
705
			if ( $user_id ) {
706
				$user = get_user_by( 'id', $user_id );
707
				if ( !$user || is_wp_error( $user ) ) {
708
					return false;
709
				}
710
			}
711
		} else {
712
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
713
			if ( !$user_id ) {
714
				return false;
715
			}
716
		}
717
718
		/* debugging
719
		error_log( "-- begin json api via jetpack debugging -- " );
720
		error_log( "METHOD: $method" );
721
		error_log( "URL: $url" );
722
		error_log( "POST BODY: $post_body" );
723
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
724
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
725
		error_log( "-- end json api via jetpack debugging -- " );
726
		*/
727
728
		if ( 'en' !== $locale ) {
729
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
730
			$new_locale = $locale;
731
			if ( strpos( $locale, '-' ) !== false ) {
732
				$locale_pieces = explode( '-', $locale );
733
				$new_locale = $locale_pieces[0];
734
				$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
735
			} else {
736
				// .com might pass 'fr' because thats what our language files are named as, where core seems
737
				// to do fr_FR - so try that if we don't think we can load the file.
738
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
739
					$new_locale =  $locale . '_' . strtoupper( $locale );
740
				}
741
			}
742
743
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
744
				unload_textdomain( 'default' );
745
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
746
			}
747
		}
748
749
		$old_user = wp_get_current_user();
750
		wp_set_current_user( $user_id );
751
752
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
753
		if ( !$token || is_wp_error( $token ) ) {
754
			return false;
755
		}
756
757
		define( 'REST_API_REQUEST', true );
758
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
759
760
		// needed?
761
		require_once ABSPATH . 'wp-admin/includes/admin.php';
762
763
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
764
		$api = WPCOM_JSON_API::init( $method, $url, $post_body );
765
		$api->token_details['user'] = $user_details;
766
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
767
768
		$display_errors = ini_set( 'display_errors', 0 );
769
		ob_start();
770
		$content_type = $api->serve( false );
771
		$output = ob_get_clean();
772
		ini_set( 'display_errors', $display_errors );
773
774
		$nonce = wp_generate_password( 10, false );
775
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
776
777
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
778
779
		return array(
780
			(string) $output,
781
			(string) $nonce,
782
			(string) $hmac,
783
		);
784
	}
785
786
	/**
787
	 * Handles authorization actions after connecting a site, such as enabling modules.
788
	 *
789
	 * This do_post_authorization() is used in this class, as opposed to calling
790
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
791
	 *
792
	 * @return void
793
	 */
794
	public function do_post_authorization() {
795
		/** This filter is documented in class.jetpack-cli.php */
796
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
797
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
798
	}
799
}
800