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