Completed
Push — update/package-tracks-move-mor... ( 03eae3...919310 )
by
unknown
112:22 queued 104:00
created

Jetpack_XMLRPC_Server::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
use Automattic\Jetpack\Tracking;
4
/**
5
 * Just a sack of functions.  Not actually an IXR_Server
6
 */
7
class Jetpack_XMLRPC_Server {
8
	/**
9
	 * The current error object
10
	 */
11
	public $error = null;
12
13
	/**
14
	 * The current user
15
	 */
16
	public $user = null;
17
	
18
	private $tracking;
19
	
20
	function __construct() {
21
		$this->tracking = new Tracking();
22
	}
23
24
	/**
25
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
26
	 * user is not authenticated (->login()) then the methods are never added,
27
	 * so they will get a "does not exist" error.
28
	 */
29
	function xmlrpc_methods( $core_methods ) {
30
		$jetpack_methods = array(
31
			'jetpack.jsonAPI'           => array( $this, 'json_api' ),
32
			'jetpack.verifyAction'      => array( $this, 'verify_action' ),
33
			'jetpack.remoteRegister'    => array( $this, 'remote_register' ),
34
			'jetpack.remoteProvision'   => array( $this, 'remote_provision' ),
35
		);
36
37
		$this->user = $this->login();
38
39
		if ( $this->user ) {
40
			$jetpack_methods = array_merge( $jetpack_methods, array(
41
				'jetpack.testConnection'    => array( $this, 'test_connection' ),
42
				'jetpack.testAPIUserCode'   => array( $this, 'test_api_user_code' ),
43
				'jetpack.featuresAvailable' => array( $this, 'features_available' ),
44
				'jetpack.featuresEnabled'   => array( $this, 'features_enabled' ),
45
				'jetpack.disconnectBlog'    => array( $this, 'disconnect_blog' ),
46
				'jetpack.unlinkUser'        => array( $this, 'unlink_user' ),
47
				'jetpack.syncObject'        => array( $this, 'sync_object' ),
48
				'jetpack.idcUrlValidation'  => array( $this, 'validate_urls_for_idc_mitigation' ),
49
			) );
50
51
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
52
				$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
53
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
54
			}
55
56
			/**
57
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
58
			 *
59
			 * @since 1.1.0
60
			 *
61
			 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
62
			 * @param array $core_methods Available core XML-RPC methods.
63
			 * @param WP_User $user Information about a given WordPress user.
64
			 */
65
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
66
		}
67
68
		/**
69
		 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
70
		 *
71
		 * @since 3.0.0
72
		 *
73
		 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
74
		 * @param array $core_methods Available core XML-RPC methods.
75
		 */
76
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
77
	}
78
79
	/**
80
	 * Whitelist of the bootstrap XML-RPC methods
81
	 */
82
	function bootstrap_xmlrpc_methods() {
83
		return array(
84
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
85
			'jetpack.remoteRegister' => array( $this, 'remote_register' ),
86
		);
87
	}
88
89
	function authorize_xmlrpc_methods() {
90
		return array(
91
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
92
		);
93
	}
94
95
	function provision_xmlrpc_methods() {
96
		return array(
97
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
98
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
99
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
100
		);
101
	}
102
103
	function remote_authorize( $request ) {
104
		$user = get_user_by( 'id', $request['state'] );
105
		$this->tracking->record_user_event( 'jpc_remote_authorize_begin', array(), $user );
106
107
		foreach( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
108
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
109
				return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ), 'jpc_remote_authorize_fail' );
110
			}
111
		}
112
113
		if ( ! $user ) {
114
			return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'jpc_remote_authorize_fail' );
115
		}
116
117
		if ( Jetpack::is_active() && Jetpack::is_user_connected( $request['state'] ) ) {
118
			return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'jpc_remote_authorize_fail' );
119
		}
120
121
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
122
123
		if ( is_a( $verified, 'IXR_Error' ) ) {
124
			return $this->error( $verified, 'jpc_remote_authorize_fail' );
125
		}
126
127
		wp_set_current_user( $request['state'] );
128
129
		$client_server = new Jetpack_Client_Server;
130
		$result = $client_server->authorize( $request );
131
132
		if ( is_wp_error( $result ) ) {
133
			return $this->error( $result, 'jpc_remote_authorize_fail' );
134
		}
135
136
		$this->tracking->record_user_event( 'jpc_remote_authorize_success' );
137
138
		return array(
139
			'result' => $result,
140
		);
141
	}
142
143
	/**
144
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
145
	 * register this site so that a plan can be provisioned.
146
	 *
147
	 * @param array $request An array containing at minimum nonce and local_user keys.
148
	 *
149
	 * @return WP_Error|array
150
	 */
151
	public function remote_register( $request ) {
152
		$this->tracking->record_user_event( 'jpc_remote_register_begin', array() );
153
154
		$user = $this->fetch_and_verify_local_user( $request );
155
156
		if ( ! $user ) {
157
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_register_fail' );
158
		}
159
160
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
161
			return $this->error( $user, 'jpc_remote_register_fail' );
162
		}
163
164
		if ( empty( $request['nonce'] ) ) {
165
			return $this->error(
166
				new Jetpack_Error(
167
					'nonce_missing',
168
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
169
					400
170
				),
171
				'jpc_remote_register_fail'
172
			);
173
		}
174
175
		$nonce = sanitize_text_field( $request['nonce'] );
176
		unset( $request['nonce'] );
177
178
		$api_url  = Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'partner_provision_nonce_check' ) );
179
		$response = Jetpack_Client::_wp_remote_request(
180
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
181
			array( 'method' => 'GET' ),
182
			true
183
		);
184
185
		if (
186
			200 !== wp_remote_retrieve_response_code( $response ) ||
187
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
188
		) {
189
			return $this->error(
190
				new Jetpack_Error(
191
					'invalid_nonce',
192
					__( 'There was an issue validating this request.', 'jetpack' ),
193
					400
194
				),
195
				'jpc_remote_register_fail'
196
			);
197
		}
198
199
		if ( ! Jetpack_Options::get_option( 'id' ) || ! Jetpack_Data::get_access_token() || ! empty( $request['force'] ) ) {
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
200
			wp_set_current_user( $user->ID );
201
202
			// This code mostly copied from Jetpack::admin_page_load.
203
			Jetpack::maybe_set_version_option();
204
			$registered = Jetpack::try_registration();
205
			if ( is_wp_error( $registered ) ) {
206
				return $this->error( $registered, 'jpc_remote_register_fail' );
207
			} elseif ( ! $registered ) {
208
				return $this->error(
209
					new Jetpack_Error(
210
						'registration_error',
211
						__( 'There was an unspecified error registering the site', 'jetpack' ),
212
						400
213
					),
214
					'jpc_remote_register_fail'
215
				);
216
			}
217
		}
218
219
		$this->tracking->record_user_event( 'jpc_remote_register_success' );
220
221
		return array(
222
			'client_id' => Jetpack_Options::get_option( 'id' )
223
		);
224
	}
225
226
	/**
227
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
228
	 * register this site so that a plan can be provisioned.
229
	 *
230
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
231
	 *
232
	 * @return WP_Error|array
233
	 */
234
	public function remote_provision( $request ) {
235
		$user = $this->fetch_and_verify_local_user( $request );
236
237
		if ( ! $user ) {
238
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_provision_fail' );
239
		}
240
241
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
242
			return $this->error( $user, 'jpc_remote_provision_fail' );
243
		}
244
245
		$site_icon = get_site_icon_url();
246
247
		$auto_enable_sso = ( ! Jetpack::is_active() || Jetpack::is_module_active( 'sso' ) );
248
249
		/** This filter is documented in class.jetpack-cli.php */
250 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
251
			$redirect_uri = add_query_arg(
252
				array(
253
					'action'      => 'jetpack-sso',
254
					'redirect_to' => rawurlencode( admin_url() ),
255
				),
256
				wp_login_url() // TODO: come back to Jetpack dashboard?
257
			);
258
		} else {
259
			$redirect_uri = admin_url();
260
		}
261
262
		// Generate secrets.
263
		$role    = Jetpack::translate_user_to_role( $user );
264
		$secrets = Jetpack::init()->generate_secrets( 'authorize', $user->ID );
265
266
		$response = array(
267
			'jp_version'   => JETPACK__VERSION,
268
			'redirect_uri' => $redirect_uri,
269
			'user_id'      => $user->ID,
270
			'user_email'   => $user->user_email,
271
			'user_login'   => $user->user_login,
272
			'scope'        => Jetpack::sign_role( $role, $user->ID ),
273
			'secret'       => $secrets['secret_1'],
274
			'is_active'    => Jetpack::is_active(),
275
		);
276
277
		if ( $site_icon ) {
278
			$response['site_icon'] = $site_icon;
279
		}
280
281
		if ( ! empty( $request['onboarding'] ) ) {
282
			Jetpack::create_onboarding_token();
283
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
284
		}
285
286
		return $response;
287
	}
288
289
	/**
290
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
291
	 * an access token for the given user.
292
	 *
293
	 * @param array $request An array containing local_user and nonce keys at minimum.
294
	 * @return mixed
295
	 */
296
	public function remote_connect( $request, $ixr_client = false ) {
297
		if ( Jetpack::is_active() ) {
298
			return $this->error(
299
				new WP_Error(
300
					'already_connected',
301
					__( 'Jetpack is already connected.', 'jetpack' ),
302
					400
303
				),
304
				'jpc_remote_connect_fail'
305
			);
306
		}
307
308
		$user = $this->fetch_and_verify_local_user( $request );
309
310
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
311
			return $this->error(
312
				new WP_Error(
313
					'input_error',
314
					__( 'Valid user is required.', 'jetpack' ),
315
					400
316
				),
317
				'jpc_remote_connect_fail'
318
			);
319
		}
320
321
		if ( empty( $request['nonce'] ) ) {
322
			return $this->error(
323
				new WP_Error(
324
					'input_error',
325
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
326
					400
327
				),
328
				'jpc_remote_connect_fail'
329
			);
330
		}
331
332
		if ( ! $ixr_client ) {
333
			Jetpack::load_xml_rpc_client();
334
			$ixr_client = new Jetpack_IXR_Client();
335
		}
336
		$ixr_client->query( 'jetpack.getUserAccessToken', array(
0 ignored issues
show
Bug introduced by
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...
337
			'nonce'            => sanitize_text_field( $request['nonce'] ),
338
			'external_user_id' => $user->ID,
339
		) );
340
341
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
342
		if ( empty( $token ) ) {
343
			return $this->error(
344
				new WP_Error(
345
					'token_fetch_failed',
346
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
347
					400
348
				),
349
				'jpc_remote_connect_fail'
350
			);
351
		}
352
		$token = sanitize_text_field( $token );
353
354
		Jetpack::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
355
356
		$this->do_post_authorization();
357
358
		return Jetpack::is_active();
359
	}
360
361
	private function fetch_and_verify_local_user( $request ) {
362
		if ( empty( $request['local_user'] ) ) {
363
			return $this->error(
364
				new Jetpack_Error(
365
					'local_user_missing',
366
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
367
					400
368
				),
369
				'jpc_remote_provision_fail'
370
			);
371
		}
372
373
		// local user is used to look up by login, email or ID
374
		$local_user_info = $request['local_user'];
375
376
		$user = get_user_by( 'login', $local_user_info );
377
378
		if ( ! $user ) {
379
			$user = get_user_by( 'email', $local_user_info );
380
		}
381
382
		if ( ! $user ) {
383
			$user = get_user_by( 'ID', $local_user_info );
384
		}
385
386
		return $user;
387
	}
388
389
	private function tracks_record_error( $name, $error, $user = null ) {
390
		if ( is_wp_error( $error ) ) {
391
			$this->tracking->record_user_event( $name, array(
392
				'error_code' => $error->get_error_code(),
393
				'error_message' => $error->get_error_message()
394
			), $user );
395
		} elseif( is_a( $error, 'IXR_Error' ) ) {
396
			$this->tracking->record_user_event( $name, array(
397
				'error_code' => $error->code,
398
				'error_message' => $error->message
399
			), $user );
400
		}
401
402
		return $error;
403
	}
404
405
	/**
406
	 * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
407
	 *
408
	 * Possible error_codes:
409
	 *
410
	 * verify_secret_1_missing
411
	 * verify_secret_1_malformed
412
	 * verify_secrets_missing: verification secrets are not found in database
413
	 * verify_secrets_incomplete: verification secrets are only partially found in database
414
	 * verify_secrets_expired: verification secrets have expired
415
	 * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
416
	 * state_missing: required parameter of state not found
417
	 * state_malformed: state is not a digit
418
	 * invalid_state: state in request does not match the stored state
419
	 *
420
	 * The 'authorize' and 'register' actions have additional error codes
421
	 *
422
	 * Possible values for action are `authorize`, `publicize` and `register`.
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
			$this->tracking->record_user_event( 'jpc_verify_authorize_begin', array(), $user );
438
		}
439
		if ( 'publicize' === $action ) {
440
			// This action is used on a response from a direct XML-RPC done from WordPress.com
441
			$tracks_failure_event_name = 'jpc_verify_publicize_fail';
442
			$this->tracking->record_user_event( 'jpc_verify_publicize_begin', array(), $user );
443
		}
444
		if ( 'register' === $action ) {
445
			$tracks_failure_event_name = 'jpc_verify_register_fail';
446
			$this->tracking->record_user_event( 'jpc_verify_register_begin', array(), $user );
447
		}
448
449
		if ( empty( $verify_secret ) ) {
450
			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 );
451
		} else if ( ! is_string( $verify_secret ) ) {
452
			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 );
453
		} else if ( empty( $state ) ) {
454
			return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user );
455
		} else if ( ! ctype_digit( $state ) ) {
456
			return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user );
457
		}
458
459
		$secrets = Jetpack::get_secrets( $action, $state );
460
461
		if ( ! $secrets ) {
462
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
463
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user );
464
		}
465
466
		if ( is_wp_error( $secrets ) ) {
467
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
468
			return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user );
469
		}
470
471
		if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) {
472
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
473
			return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user );
474
		}
475
476
		if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) {
477
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
478
			return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user );
479
		}
480
481
		Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
482
483
		if ( 'authorize' === $action ) {
484
			$this->tracking->record_user_event( 'jpc_verify_authorize_success', array(), $user );
485
		}
486
		if ( 'publicize' === $action ) {
487
			$this->tracking->record_user_event( 'jpc_verify_publicize_success', array(), $user );
488
		}
489
		if ( 'register' === $action ) {
490
			$this->tracking->record_user_event( 'jpc_verify_register_success', array(), $user );
491
		}
492
493
		return $secrets['secret_2'];
494
	}
495
496
	/**
497
	 * Wrapper for wp_authenticate( $username, $password );
498
	 *
499
	 * @return WP_User|bool
500
	 */
501
	function login() {
502
		Jetpack::init()->require_jetpack_authentication();
503
		$user = wp_authenticate( 'username', 'password' );
504
		if ( is_wp_error( $user ) ) {
505
			if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything.
506
				$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
507
			} else {
508
				$this->error = $user;
509
			}
510
			return false;
511
		} else if ( !$user ) { // Shouldn't happen.
512
			$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
513
			return false;
514
		}
515
516
		return $user;
517
	}
518
519
	/**
520
	 * Returns the current error as an IXR_Error
521
	 *
522
	 * @return bool|IXR_Error
523
	 */
524
	function error( $error = null, $tracks_event_name = null, $user = null ) {
525
		// record using Tracks
526
		if ( null !== $tracks_event_name ) {
527
			$this->tracks_record_error( $tracks_event_name, $error, $user );
528
		}
529
530
		if ( !is_null( $error ) ) {
531
			$this->error = $error;
532
		}
533
534
		if ( is_wp_error( $this->error ) ) {
535
			$code = $this->error->get_error_data();
536
			if ( !$code ) {
537
				$code = -10520;
538
			}
539
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
540
			return new IXR_Error( $code, $message );
541
		} else if ( is_a( $this->error, 'IXR_Error' ) ) {
542
			return $this->error;
543
		}
544
545
		return false;
546
	}
547
548
/* API Methods */
549
550
	/**
551
	 * Just authenticates with the given Jetpack credentials.
552
	 *
553
	 * @return string The current Jetpack version number
554
	 */
555
	function test_connection() {
556
		return JETPACK__VERSION;
557
	}
558
559
	function test_api_user_code( $args ) {
560
		$client_id = (int) $args[0];
561
		$user_id   = (int) $args[1];
562
		$nonce     = (string) $args[2];
563
		$verify    = (string) $args[3];
564
565
		if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
566
			return false;
567
		}
568
569
		$user = get_user_by( 'id', $user_id );
570
		if ( !$user || is_wp_error( $user ) ) {
571
			return false;
572
		}
573
574
		/* debugging
575
		error_log( "CLIENT: $client_id" );
576
		error_log( "USER:   $user_id" );
577
		error_log( "NONCE:  $nonce" );
578
		error_log( "VERIFY: $verify" );
579
		*/
580
581
		$jetpack_token = Jetpack_Data::get_access_token( $user_id );
0 ignored issues
show
Documentation introduced by
$user_id is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
582
583
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
584
		if ( !$api_user_code ) {
585
			return false;
586
		}
587
588
		$hmac = hash_hmac( 'md5', json_encode( (object) array(
589
			'client_id' => (int) $client_id,
590
			'user_id'   => (int) $user_id,
591
			'nonce'     => (string) $nonce,
592
			'code'      => (string) $api_user_code,
593
		) ), $jetpack_token->secret );
594
595
		if ( ! hash_equals( $hmac, $verify ) ) {
596
			return false;
597
		}
598
599
		return $user_id;
600
	}
601
602
	/**
603
	* Disconnect this blog from the connected wordpress.com account
604
	* @return boolean
605
	*/
606
	function disconnect_blog() {
607
608
		// For tracking
609
		if ( ! empty( $this->user->ID ) ) {
610
			wp_set_current_user( $this->user->ID );
611
		}
612
613
		Jetpack::log( 'disconnect' );
614
		Jetpack::disconnect();
615
616
		return true;
617
	}
618
619
	/**
620
	 * Unlink a user from WordPress.com
621
	 *
622
	 * This will fail if called by the Master User.
623
	 */
624
	function unlink_user() {
625
		Jetpack::log( 'unlink' );
626
		return Jetpack::unlink_user();
627
	}
628
629
	/**
630
	 * Returns any object that is able to be synced
631
	 */
632
	function sync_object( $args ) {
633
		// e.g. posts, post, 5
634
		list( $module_name, $object_type, $id ) = $args;
635
636
		$sync_module = Jetpack_Sync_Modules::get_module( $module_name );
637
		$codec = Jetpack_Sync_Sender::get_instance()->get_codec();
638
639
		return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
640
	}
641
642
	/**
643
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
644
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
645
	 * and the remote Jetpack site.
646
	 *
647
	 * @return array
648
	 */
649
	function validate_urls_for_idc_mitigation() {
650
		return array(
651
			'home'    => Jetpack_Sync_Functions::home_url(),
652
			'siteurl' => Jetpack_Sync_Functions::site_url(),
653
		);
654
	}
655
656
	/**
657
	 * Returns what features are available. Uses the slug of the module files.
658
	 *
659
	 * @return array
660
	 */
661 View Code Duplication
	function features_available() {
662
		$raw_modules = Jetpack::get_available_modules();
663
		$modules = array();
664
		foreach ( $raw_modules as $module ) {
665
			$modules[] = Jetpack::get_module_slug( $module );
666
		}
667
668
		return $modules;
669
	}
670
671
	/**
672
	 * Returns what features are enabled. Uses the slug of the modules files.
673
	 *
674
	 * @return array
675
	 */
676 View Code Duplication
	function features_enabled() {
677
		$raw_modules = Jetpack::get_active_modules();
678
		$modules = array();
679
		foreach ( $raw_modules as $module ) {
680
			$modules[] = Jetpack::get_module_slug( $module );
681
		}
682
683
		return $modules;
684
	}
685
686
	function update_attachment_parent( $args ) {
687
		$attachment_id = (int) $args[0];
688
		$parent_id     = (int) $args[1];
689
690
		return wp_update_post( array(
691
			'ID'          => $attachment_id,
692
			'post_parent' => $parent_id,
693
		) );
694
	}
695
696
	function json_api( $args = array() ) {
697
		$json_api_args = $args[0];
698
		$verify_api_user_args = $args[1];
699
700
		$method       = (string) $json_api_args[0];
701
		$url          = (string) $json_api_args[1];
702
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
703
		$user_details = (array) $json_api_args[4];
704
		$locale       = (string) $json_api_args[5];
705
706
		if ( !$verify_api_user_args ) {
707
			$user_id = 0;
708
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
709
			$user_id = (int) $verify_api_user_args[1];
710
			if ( $user_id ) {
711
				$user = get_user_by( 'id', $user_id );
712
				if ( !$user || is_wp_error( $user ) ) {
713
					return false;
714
				}
715
			}
716
		} else {
717
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
718
			if ( !$user_id ) {
719
				return false;
720
			}
721
		}
722
723
		/* debugging
724
		error_log( "-- begin json api via jetpack debugging -- " );
725
		error_log( "METHOD: $method" );
726
		error_log( "URL: $url" );
727
		error_log( "POST BODY: $post_body" );
728
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
729
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
730
		error_log( "-- end json api via jetpack debugging -- " );
731
		*/
732
733
		if ( 'en' !== $locale ) {
734
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
735
			$new_locale = $locale;
736
			if ( strpos( $locale, '-' ) !== false ) {
737
				$locale_pieces = explode( '-', $locale );
738
				$new_locale = $locale_pieces[0];
739
				$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
740
			} else {
741
				// .com might pass 'fr' because thats what our language files are named as, where core seems
742
				// to do fr_FR - so try that if we don't think we can load the file.
743
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
744
					$new_locale =  $locale . '_' . strtoupper( $locale );
745
				}
746
			}
747
748
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
749
				unload_textdomain( 'default' );
750
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
751
			}
752
		}
753
754
		$old_user = wp_get_current_user();
755
		wp_set_current_user( $user_id );
756
757
		if ( $user_id ) {
758
			$token_key = false;
759
		} else {
760
			$jetpack   = Jetpack::init();
761
			$verified  = $jetpack->verify_xml_rpc_signature();
762
			$token_key = $verified['token_key'];
763
		}
764
765
		$token = Jetpack_Data::get_access_token( $user_id, $token_key );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
766
		if ( !$token || is_wp_error( $token ) ) {
767
			return false;
768
		}
769
770
		define( 'REST_API_REQUEST', true );
771
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
772
773
		// needed?
774
		require_once ABSPATH . 'wp-admin/includes/admin.php';
775
776
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
777
		$api = WPCOM_JSON_API::init( $method, $url, $post_body );
778
		$api->token_details['user'] = $user_details;
779
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
780
781
		$display_errors = ini_set( 'display_errors', 0 );
782
		ob_start();
783
		$content_type = $api->serve( false );
0 ignored issues
show
Unused Code introduced by
$content_type is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
784
		$output = ob_get_clean();
785
		ini_set( 'display_errors', $display_errors );
786
787
		$nonce = wp_generate_password( 10, false );
788
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
789
790
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
791
792
		return array(
793
			(string) $output,
794
			(string) $nonce,
795
			(string) $hmac,
796
		);
797
	}
798
799
	/**
800
	 * Handles authorization actions after connecting a site, such as enabling modules.
801
	 *
802
	 * This do_post_authorization() is used in this class, as opposed to calling
803
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
804
	 *
805
	 * @return void
806
	 */
807
	public function do_post_authorization() {
808
		/** This filter is documented in class.jetpack-cli.php */
809
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
810
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
811
	}
812
}
813