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