Completed
Push — fix/block-vr ( dd4d13...b6c320 )
by Bernhard
196:37 queued 183:38
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_register_fail' );
233
		}
234
235
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
236
			return $this->error( $user, 'jpc_remote_register_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 View Code Duplication
		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_register_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(
0 ignored issues
show
It seems like $ixr_client is not always an object, but can also be of type boolean. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
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 View Code Duplication
	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 View Code Duplication
	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