Completed
Push — fix/check-queue-status-before-... ( fa5495...b1b857 )
by
unknown
08:17 queued 14s
created

Jetpack_XMLRPC_Server::bootstrap_xmlrpc_methods()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Jetpack XMLRPC Server.
4
 *
5
 * @package automattic/jetpack-connection
6
 */
7
8
use Automattic\Jetpack\Connection\Client;
9
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
10
use Automattic\Jetpack\Roles;
11
use Automattic\Jetpack\Sync\Modules;
12
use Automattic\Jetpack\Sync\Functions;
13
use Automattic\Jetpack\Sync\Sender;
14
use Automattic\Jetpack\Tracking;
15
16
/**
17
 * Just a sack of functions.  Not actually an IXR_Server
18
 */
19
class Jetpack_XMLRPC_Server {
20
	/**
21
	 * The current error object
22
	 *
23
	 * @var \WP_Error
24
	 */
25
	public $error = null;
26
27
	/**
28
	 * The current user
29
	 *
30
	 * @var \WP_User
31
	 */
32
	public $user = null;
33
34
	/**
35
	 * The tracking manager object.
36
	 *
37
	 * @var Automattic\Jetpack\Tracking
38
	 */
39
	private $tracking;
40
41
	/**
42
	 * The connection manager object.
43
	 *
44
	 * @var Automattic\Jetpack\Connection\Manager
45
	 */
46
	private $connection;
47
48
	/**
49
	 * Creates a new XMLRPC server object.
50
	 *
51
	 * @param Automattic\Jetpack\Connection\Manager $manager the connection manager object.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $manager not be Connection_Manager|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
52
	 */
53
	public function __construct( $manager = null ) {
54
		$this->connection = is_null( $manager ) ? new Connection_Manager() : $manager;
55
		$this->tracking   = new Tracking( 'jetpack', $manager );
0 ignored issues
show
Bug introduced by
It seems like $manager defined by parameter $manager on line 53 can also be of type object<Automattic\Jetpack\Connection\Manager>; however, Automattic\Jetpack\Tracking::__construct() does only seem to accept object<Automattic\Jetpac...onnection\Manager>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
56
	}
57
58
	/**
59
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
60
	 * user is not authenticated (->login()) then the methods are never added,
61
	 * so they will get a "does not exist" error.
62
	 *
63
	 * @param array $core_methods Core XMLRPC methods.
64
	 */
65
	public function xmlrpc_methods( $core_methods ) {
66
		$jetpack_methods = array(
67
			'jetpack.jsonAPI'         => array( $this, 'json_api' ),
68
			'jetpack.verifyAction'    => array( $this, 'verify_action' ),
69
			'jetpack.getUser'         => array( $this, 'get_user' ),
70
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
71
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
72
		);
73
74
		$this->user = $this->login();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->login() can also be of type boolean. However, the property $user is declared as type object<WP_User>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
75
76
		if ( $this->user ) {
77
			$jetpack_methods = array_merge(
78
				$jetpack_methods,
79
				array(
80
					'jetpack.testConnection'    => array( $this, 'test_connection' ),
81
					'jetpack.testAPIUserCode'   => array( $this, 'test_api_user_code' ),
82
					'jetpack.featuresAvailable' => array( $this, 'features_available' ),
83
					'jetpack.featuresEnabled'   => array( $this, 'features_enabled' ),
84
					'jetpack.disconnectBlog'    => array( $this, 'disconnect_blog' ),
85
					'jetpack.unlinkUser'        => array( $this, 'unlink_user' ),
86
					'jetpack.idcUrlValidation'  => array( $this, 'validate_urls_for_idc_mitigation' ),
87
				)
88
			);
89
90
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
91
				$jetpack_methods['metaWeblog.newMediaObject']      = $core_methods['metaWeblog.newMediaObject'];
92
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
93
			}
94
95
			/**
96
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
97
			 *
98
			 * @since 1.1.0
99
			 *
100
			 * @param array    $jetpack_methods XML-RPC methods available to the Jetpack Server.
101
			 * @param array    $core_methods    Available core XML-RPC methods.
102
			 * @param \WP_User $user            Information about a given WordPress user.
103
			 */
104
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
105
		}
106
107
		/**
108
		 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
109
		 *
110
		 * @since 3.0.0
111
		 *
112
		 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
113
		 * @param array $core_methods    Available core XML-RPC methods.
114
		 */
115
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
116
	}
117
118
	/**
119
	 * Whitelist of the bootstrap XML-RPC methods
120
	 */
121
	public function bootstrap_xmlrpc_methods() {
122
		return array(
123
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
124
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
125
		);
126
	}
127
128
	/**
129
	 * Additional method needed for authorization calls.
130
	 */
131
	public function authorize_xmlrpc_methods() {
132
		return array(
133
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
134
		);
135
	}
136
137
	/**
138
	 * Remote provisioning methods.
139
	 */
140
	public function provision_xmlrpc_methods() {
141
		return array(
142
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
143
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
144
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
145
			'jetpack.getUser'         => array( $this, 'get_user' ),
146
		);
147
	}
148
149
	/**
150
	 * Used to verify whether a local user exists and what role they have.
151
	 *
152
	 * @param int|string|array $request One of:
153
	 *                         int|string The local User's ID, username, or email address.
154
	 *                         array      A request array containing:
155
	 *                                    0: int|string The local User's ID, username, or email address.
156
	 *
157
	 * @return array|\IXR_Error Information about the user, or error if no such user found:
158
	 *                          roles:     string[] The user's rols.
159
	 *                          login:     string   The user's username.
160
	 *                          email_hash string[] The MD5 hash of the user's normalized email address.
161
	 *                          caps       string[] The user's capabilities.
162
	 *                          allcaps    string[] The user's granular capabilities, merged from role capabilities.
163
	 *                          token_key  string   The Token Key of the user's Jetpack token. Empty string if none.
164
	 */
165
	public function get_user( $request ) {
166
		$user_id = is_array( $request ) ? $request[0] : $request;
167
168
		if ( ! $user_id ) {
169
			return $this->error(
170
				new Jetpack_Error(
171
					'invalid_user',
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'invalid_user'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
172
					__( 'Invalid user identifier.', 'jetpack' ),
173
					400
174
				),
175
				'jpc_get_user_fail'
176
			);
177
		}
178
179
		$user = $this->get_user_by_anything( $user_id );
180
181
		if ( ! $user ) {
182
			return $this->error(
183
				new Jetpack_Error(
184
					'user_unknown',
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'user_unknown'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
185
					__( 'User not found.', 'jetpack' ),
186
					404
187
				),
188
				'jpc_get_user_fail'
189
			);
190
		}
191
192
		$user_token = $this->connection->get_access_token( $user->ID );
193
194
		if ( $user_token ) {
195
			list( $user_token_key ) = explode( '.', $user_token->secret );
196
			if ( $user_token_key === $user_token->secret ) {
197
				$user_token_key = '';
198
			}
199
		} else {
200
			$user_token_key = '';
201
		}
202
203
		return array(
204
			'id'         => $user->ID,
205
			'login'      => $user->user_login,
206
			'email_hash' => md5( strtolower( trim( $user->user_email ) ) ),
207
			'roles'      => $user->roles,
208
			'caps'       => $user->caps,
209
			'allcaps'    => $user->allcaps,
210
			'token_key'  => $user_token_key,
211
		);
212
	}
213
214
	/**
215
	 * Remote authorization XMLRPC method handler.
216
	 *
217
	 * @param array $request the request.
218
	 */
219
	public function remote_authorize( $request ) {
220
		$user = get_user_by( 'id', $request['state'] );
221
		$this->tracking->record_user_event( 'jpc_remote_authorize_begin', array(), $user );
222
223
		foreach ( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
224
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
225
				return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ), 'jpc_remote_authorize_fail' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'missing_parameter'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
226
			}
227
		}
228
229
		if ( ! $user ) {
230
			return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'jpc_remote_authorize_fail' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'user_unknown'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
231
		}
232
233
		if ( $this->connection->is_active() && $this->connection->is_user_connected( $request['state'] ) ) {
234
			return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'jpc_remote_authorize_fail' );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'already_connected'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
235
		}
236
237
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
238
239
		if ( is_a( $verified, 'IXR_Error' ) ) {
240
			return $this->error( $verified, 'jpc_remote_authorize_fail' );
0 ignored issues
show
Bug introduced by
It seems like $verified defined by $this->verify_action(arr...'], $request['state'])) on line 237 can also be of type string; however, Jetpack_XMLRPC_Server::error() does only seem to accept object<WP_Error>|object<IXR_Error>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
241
		}
242
243
		wp_set_current_user( $request['state'] );
244
245
		$client_server = new Jetpack_Client_Server();
246
		$result        = $client_server->authorize( $request );
247
248
		if ( is_wp_error( $result ) ) {
249
			return $this->error( $result, 'jpc_remote_authorize_fail' );
0 ignored issues
show
Bug introduced by
It seems like $result defined by $client_server->authorize($request) on line 246 can also be of type string; however, Jetpack_XMLRPC_Server::error() does only seem to accept object<WP_Error>|object<IXR_Error>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
250
		}
251
252
		$this->tracking->record_user_event( 'jpc_remote_authorize_success' );
253
254
		return array(
255
			'result' => $result,
256
		);
257
	}
258
259
	/**
260
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
261
	 * register this site so that a plan can be provisioned.
262
	 *
263
	 * @param array $request An array containing at minimum nonce and local_user keys.
264
	 *
265
	 * @return \WP_Error|array
266
	 */
267
	public function remote_register( $request ) {
268
		$this->tracking->record_user_event( 'jpc_remote_register_begin', array() );
269
270
		$user = $this->fetch_and_verify_local_user( $request );
271
272
		if ( ! $user ) {
273
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_register_fail' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'input_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
274
		}
275
276
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
277
			return $this->error( $user, 'jpc_remote_register_fail' );
278
		}
279
280
		if ( empty( $request['nonce'] ) ) {
281
			return $this->error(
282
				new Jetpack_Error(
283
					'nonce_missing',
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'nonce_missing'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
284
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
285
					400
286
				),
287
				'jpc_remote_register_fail'
288
			);
289
		}
290
291
		$nonce = sanitize_text_field( $request['nonce'] );
292
		unset( $request['nonce'] );
293
294
		$api_url  = Jetpack::fix_url_for_bad_hosts(
295
			$this->connection->api_url( 'partner_provision_nonce_check' )
296
		);
297
		$response = Client::_wp_remote_request(
298
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
299
			array( 'method' => 'GET' ),
300
			true
301
		);
302
303
		if (
304
			200 !== wp_remote_retrieve_response_code( $response ) ||
305
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
306
		) {
307
			return $this->error(
308
				new Jetpack_Error(
309
					'invalid_nonce',
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'invalid_nonce'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
310
					__( 'There was an issue validating this request.', 'jetpack' ),
311
					400
312
				),
313
				'jpc_remote_register_fail'
314
			);
315
		}
316
317
		if ( ! Jetpack_Options::get_option( 'id' ) || ! $this->connection->get_access_token() || ! empty( $request['force'] ) ) {
318
			wp_set_current_user( $user->ID );
319
320
			// This code mostly copied from Jetpack::admin_page_load.
321
			Jetpack::maybe_set_version_option();
322
			$registered = Jetpack::try_registration();
323
			if ( is_wp_error( $registered ) ) {
324
				return $this->error( $registered, 'jpc_remote_register_fail' );
325
			} elseif ( ! $registered ) {
326
				return $this->error(
327
					new Jetpack_Error(
328
						'registration_error',
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'registration_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
329
						__( 'There was an unspecified error registering the site', 'jetpack' ),
330
						400
331
					),
332
					'jpc_remote_register_fail'
333
				);
334
			}
335
		}
336
337
		$this->tracking->record_user_event( 'jpc_remote_register_success' );
338
339
		return array(
340
			'client_id' => Jetpack_Options::get_option( 'id' ),
341
		);
342
	}
343
344
	/**
345
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
346
	 * register this site so that a plan can be provisioned.
347
	 *
348
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
349
	 *
350
	 * @return \WP_Error|array
351
	 */
352
	public function remote_provision( $request ) {
353
		$user = $this->fetch_and_verify_local_user( $request );
354
355
		if ( ! $user ) {
356
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_provision_fail' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'input_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
357
		}
358
359
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
360
			return $this->error( $user, 'jpc_remote_provision_fail' );
361
		}
362
363
		$site_icon = get_site_icon_url();
364
365
		$auto_enable_sso = ( ! $this->connection->is_active() || Jetpack::is_module_active( 'sso' ) );
366
367
		/** This filter is documented in class.jetpack-cli.php */
368 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
369
			$redirect_uri = add_query_arg(
370
				array(
371
					'action'      => 'jetpack-sso',
372
					'redirect_to' => rawurlencode( admin_url() ),
373
				),
374
				wp_login_url() // TODO: come back to Jetpack dashboard?
375
			);
376
		} else {
377
			$redirect_uri = admin_url();
378
		}
379
380
		// Generate secrets.
381
		$roles   = new Roles();
382
		$role    = $roles->translate_user_to_role( $user );
383
		$secrets = $this->connection->generate_secrets( 'authorize', $user->ID );
384
385
		$response = array(
386
			'jp_version'   => JETPACK__VERSION,
387
			'redirect_uri' => $redirect_uri,
388
			'user_id'      => $user->ID,
389
			'user_email'   => $user->user_email,
390
			'user_login'   => $user->user_login,
391
			'scope'        => $this->connection->sign_role( $role, $user->ID ),
392
			'secret'       => $secrets['secret_1'],
393
			'is_active'    => $this->connection->is_active(),
394
		);
395
396
		if ( $site_icon ) {
397
			$response['site_icon'] = $site_icon;
398
		}
399
400
		if ( ! empty( $request['onboarding'] ) ) {
401
			Jetpack::create_onboarding_token();
402
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
403
		}
404
405
		return $response;
406
	}
407
408
	/**
409
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
410
	 * an access token for the given user.
411
	 *
412
	 * @param array       $request    An array containing local_user and nonce keys at minimum.
413
	 * @param \IXR_Client $ixr_client The client object, optional.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $ixr_client not be false|IXR_Client?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
414
	 * @return mixed
415
	 */
416
	public function remote_connect( $request, $ixr_client = false ) {
417
		if ( $this->connection->is_active() ) {
418
			return $this->error(
419
				new WP_Error(
420
					'already_connected',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'already_connected'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
421
					__( 'Jetpack is already connected.', 'jetpack' ),
422
					400
423
				),
424
				'jpc_remote_connect_fail'
425
			);
426
		}
427
428
		$user = $this->fetch_and_verify_local_user( $request );
429
430
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
431
			return $this->error(
432
				new WP_Error(
433
					'input_error',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'input_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
434
					__( 'Valid user is required.', 'jetpack' ),
435
					400
436
				),
437
				'jpc_remote_connect_fail'
438
			);
439
		}
440
441
		if ( empty( $request['nonce'] ) ) {
442
			return $this->error(
443
				new WP_Error(
444
					'input_error',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'input_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
445
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
446
					400
447
				),
448
				'jpc_remote_connect_fail'
449
			);
450
		}
451
452
		if ( ! $ixr_client ) {
453
			$ixr_client = new Jetpack_IXR_Client();
454
		}
455
		$ixr_client->query(
456
			'jetpack.getUserAccessToken',
457
			array(
458
				'nonce'            => sanitize_text_field( $request['nonce'] ),
459
				'external_user_id' => $user->ID,
460
			)
461
		);
462
463
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
464
		if ( empty( $token ) ) {
465
			return $this->error(
466
				new WP_Error(
467
					'token_fetch_failed',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'token_fetch_failed'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
468
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
469
					400
470
				),
471
				'jpc_remote_connect_fail'
472
			);
473
		}
474
		$token = sanitize_text_field( $token );
475
476
		Jetpack::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
477
478
		$this->do_post_authorization();
479
480
		return $this->connection->is_active();
481
	}
482
483
	/**
484
	 * Getter for the local user to act as.
485
	 *
486
	 * @param array $request the current request data.
487
	 */
488
	private function fetch_and_verify_local_user( $request ) {
489
		if ( empty( $request['local_user'] ) ) {
490
			return $this->error(
491
				new Jetpack_Error(
492
					'local_user_missing',
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'local_user_missing'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
493
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
494
					400
495
				),
496
				'jpc_remote_provision_fail'
497
			);
498
		}
499
500
		// Local user is used to look up by login, email or ID.
501
		$local_user_info = $request['local_user'];
502
503
		return $this->get_user_by_anything( $local_user_info );
504
	}
505
506
	/**
507
	 * Gets the user object by its data.
508
	 *
509
	 * @param string $user_id can be any identifying user data.
510
	 */
511
	private function get_user_by_anything( $user_id ) {
512
		$user = get_user_by( 'login', $user_id );
513
514
		if ( ! $user ) {
515
			$user = get_user_by( 'email', $user_id );
516
		}
517
518
		if ( ! $user ) {
519
			$user = get_user_by( 'ID', $user_id );
520
		}
521
522
		return $user;
523
	}
524
525
	/**
526
	 * Track an error.
527
	 *
528
	 * @param string               $name  Event name.
529
	 * @param \WP_Error|\IXR_Error $error The error object.
530
	 * @param \WP_User             $user  The user object.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user not be WP_User|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
531
	 */
532
	private function tracks_record_error( $name, $error, $user = null ) {
533
		if ( is_wp_error( $error ) ) {
534
			$this->tracking->record_user_event(
535
				$name,
536
				array(
537
					'error_code'    => $error->get_error_code(),
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
538
					'error_message' => $error->get_error_message(),
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
539
				),
540
				$user
541
			);
542
		} elseif ( is_a( $error, '\\IXR_Error' ) ) {
543
			$this->tracking->record_user_event(
544
				$name,
545
				array(
546
					'error_code'    => $error->code,
547
					'error_message' => $error->message,
548
				),
549
				$user
550
			);
551
		}
552
553
		return $error;
554
	}
555
556
	/**
557
	 * Possible error_codes:
558
	 *
559
	 * - verify_secret_1_missing
560
	 * - verify_secret_1_malformed
561
	 * - verify_secrets_missing: verification secrets are not found in database
562
	 * - verify_secrets_incomplete: verification secrets are only partially found in database
563
	 * - verify_secrets_expired: verification secrets have expired
564
	 * - verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
565
	 * - state_missing: required parameter of state not found
566
	 * - state_malformed: state is not a digit
567
	 * - invalid_state: state in request does not match the stored state
568
	 *
569
	 * The 'authorize' and 'register' actions have additional error codes
570
	 *
571
	 * Possible values for action are `authorize`, `publicize` and `register`.
572
	 *
573
	 * state_missing: a state ( user id ) was not supplied
574
	 * state_malformed: state is not the correct data type
575
	 * invalid_state: supplied state does not match the stored state
576
	 *
577
	 * @param array $params action parameters.
578
	 * @return \WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
579
	 */
580
	public function verify_action( $params ) {
581
		$action                    = $params[0];
582
		$verify_secret             = $params[1];
583
		$state                     = isset( $params[2] ) ? $params[2] : '';
584
		$user                      = get_user_by( 'id', $state );
585
		$tracks_failure_event_name = '';
586
587
		if ( 'authorize' === $action ) {
588
			$tracks_failure_event_name = 'jpc_verify_authorize_fail';
589
			$this->tracking->record_user_event( 'jpc_verify_authorize_begin', array(), $user );
590
		}
591
		if ( 'publicize' === $action ) {
592
			// This action is used on a response from a direct XML-RPC done from WordPress.com.
593
			$tracks_failure_event_name = 'jpc_verify_publicize_fail';
594
			$this->tracking->record_user_event( 'jpc_verify_publicize_begin', array(), $user );
595
		}
596
		if ( 'register' === $action ) {
597
			$tracks_failure_event_name = 'jpc_verify_register_fail';
598
			$this->tracking->record_user_event( 'jpc_verify_register_begin', array(), $user );
599
		}
600
601
		if ( empty( $verify_secret ) ) {
602
			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 );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'verify_secret_1_missing'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
603
		} elseif ( ! is_string( $verify_secret ) ) {
604
			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 );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'verify_secret_1_malformed'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
605
		} elseif ( empty( $state ) ) {
606
			return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'state_missing'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
607
		} elseif ( ! ctype_digit( $state ) ) {
608
			return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'state_malformed'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
609
		}
610
611
		$secrets = Jetpack::get_secrets( $action, $state );
612
613
		if ( ! $secrets ) {
614
			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...
615
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'verify_secrets_missing'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
616
		}
617
618
		if ( is_wp_error( $secrets ) ) {
619
			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...
620
			return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user );
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with $secrets->get_error_code().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
621
		}
622
623
		if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) {
624
			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...
625
			return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'verify_secrets_incomplete'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
626
		}
627
628
		if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) {
629
			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...
630
			return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'verify_secrets_mismatch'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
631
		}
632
633
		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...
634
635
		if ( 'authorize' === $action ) {
636
			$this->tracking->record_user_event( 'jpc_verify_authorize_success', array(), $user );
637
		}
638
		if ( 'publicize' === $action ) {
639
			$this->tracking->record_user_event( 'jpc_verify_publicize_success', array(), $user );
640
		}
641
		if ( 'register' === $action ) {
642
			$this->tracking->record_user_event( 'jpc_verify_register_success', array(), $user );
643
		}
644
645
		return $secrets['secret_2'];
646
	}
647
648
	/**
649
	 * Wrapper for wp_authenticate( $username, $password );
650
	 *
651
	 * @return \WP_User|bool
652
	 */
653
	public function login() {
654
		$this->connection->require_jetpack_authentication();
655
		$user = wp_authenticate( 'username', 'password' );
656
		if ( is_wp_error( $user ) ) {
657
			if ( 'authentication_failed' === $user->get_error_code() ) { // Generic error could mean most anything.
658
				$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'invalid_request'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
659
			} else {
660
				$this->error = $user;
661
			}
662
			return false;
663
		} elseif ( ! $user ) { // Shouldn't happen.
664
			$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Error::__construct() has too many arguments starting with 'invalid_request'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
665
			return false;
666
		}
667
668
		return $user;
669
	}
670
671
	/**
672
	 * Returns the current error as an \IXR_Error
673
	 *
674
	 * @param \WP_Error|\IXR_Error $error             The error object, optional.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $error not be WP_Error|IXR_Error|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
675
	 * @param string               $tracks_event_name The event name.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $tracks_event_name not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
676
	 * @param \WP_User             $user              The user object.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user not be WP_User|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
677
	 * @return bool|\IXR_Error
678
	 */
679
	public function error( $error = null, $tracks_event_name = null, $user = null ) {
680
		// Record using Tracks.
681
		if ( null !== $tracks_event_name ) {
682
			$this->tracks_record_error( $tracks_event_name, $error, $user );
0 ignored issues
show
Bug introduced by
It seems like $error defined by parameter $error on line 679 can also be of type null; however, Jetpack_XMLRPC_Server::tracks_record_error() does only seem to accept object<WP_Error>|object<IXR_Error>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
683
		}
684
685
		if ( ! is_null( $error ) ) {
686
			$this->error = $error;
0 ignored issues
show
Documentation Bug introduced by
It seems like $error can also be of type object<IXR_Error>. However, the property $error is declared as type object<WP_Error>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
687
		}
688
689
		if ( is_wp_error( $this->error ) ) {
690
			$code = $this->error->get_error_data();
0 ignored issues
show
Bug introduced by
The method get_error_data() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
691
			if ( ! $code ) {
692
				$code = -10520;
693
			}
694
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
695
			return new \IXR_Error( $code, $message );
696
		} elseif ( is_a( $this->error, 'IXR_Error' ) ) {
697
			return $this->error;
698
		}
699
700
		return false;
701
	}
702
703
	/* API Methods */
704
705
	/**
706
	 * Just authenticates with the given Jetpack credentials.
707
	 *
708
	 * @return string The current Jetpack version number
709
	 */
710
	public function test_connection() {
711
		return JETPACK__VERSION;
712
	}
713
714
	/**
715
	 * Test the API user code.
716
	 *
717
	 * @param array $args arguments identifying the test site.
718
	 */
719
	public function test_api_user_code( $args ) {
720
		$client_id = (int) $args[0];
721
		$user_id   = (int) $args[1];
722
		$nonce     = (string) $args[2];
723
		$verify    = (string) $args[3];
724
725
		if ( ! $client_id || ! $user_id || ! strlen( $nonce ) || 32 !== strlen( $verify ) ) {
726
			return false;
727
		}
728
729
		$user = get_user_by( 'id', $user_id );
730
		if ( ! $user || is_wp_error( $user ) ) {
731
			return false;
732
		}
733
734
		/* phpcs:ignore
735
		 debugging
736
		error_log( "CLIENT: $client_id" );
737
		error_log( "USER:   $user_id" );
738
		error_log( "NONCE:  $nonce" );
739
		error_log( "VERIFY: $verify" );
740
		*/
741
742
		$jetpack_token = $this->connection->get_access_token( $user_id );
743
744
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
745
		if ( ! $api_user_code ) {
746
			return false;
747
		}
748
749
		$hmac = hash_hmac(
750
			'md5',
751
			json_encode( // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
752
				(object) array(
753
					'client_id' => (int) $client_id,
754
					'user_id'   => (int) $user_id,
755
					'nonce'     => (string) $nonce,
756
					'code'      => (string) $api_user_code,
757
				)
758
			),
759
			$jetpack_token->secret
760
		);
761
762
		if ( ! hash_equals( $hmac, $verify ) ) {
763
			return false;
764
		}
765
766
		return $user_id;
767
	}
768
769
	/**
770
	 * Disconnect this blog from the connected wordpress.com account
771
	 *
772
	 * @return boolean
773
	 */
774
	public function disconnect_blog() {
775
776
		// For tracking.
777
		if ( ! empty( $this->user->ID ) ) {
778
			wp_set_current_user( $this->user->ID );
779
		}
780
781
		/**
782
		 * Fired when we want to log an event to the Jetpack event log.
783
		 *
784
		 * @since 7.7.0
785
		 *
786
		 * @param string $code Unique name for the event.
787
		 * @param string $data Optional data about the event.
788
		 */
789
		do_action( 'jetpack_event_log', 'disconnect' );
790
		Jetpack::disconnect();
791
792
		return true;
793
	}
794
795
	/**
796
	 * Unlink a user from WordPress.com
797
	 *
798
	 * This will fail if called by the Master User.
799
	 */
800
	public function unlink_user() {
801
		/**
802
		 * Fired when we want to log an event to the Jetpack event log.
803
		 *
804
		 * @since 7.7.0
805
		 *
806
		 * @param string $code Unique name for the event.
807
		 * @param string $data Optional data about the event.
808
		 */
809
		do_action( 'jetpack_event_log', 'unlink' );
810
		return Connection_Manager::disconnect_user();
811
	}
812
813
	/**
814
	 * Returns any object that is able to be synced.
815
	 *
816
	 * @deprecated since 7.8.0
817
	 * @see Automattic\Jetpack\Sync\Sender::sync_object()
818
	 *
819
	 * @param array $args the synchronized object parameters.
820
	 * @return string Encoded sync object.
821
	 */
822
	public function sync_object( $args ) {
823
		_deprecated_function( __METHOD__, 'jetpack-7.8', 'Automattic\\Jetpack\\Sync\\Sender::sync_object' );
824
		return Sender::get_instance()->sync_object( $args );
825
	}
826
827
	/**
828
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
829
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
830
	 * and the remote Jetpack site.
831
	 *
832
	 * @return array
833
	 */
834
	public function validate_urls_for_idc_mitigation() {
835
		return array(
836
			'home'    => Functions::home_url(),
837
			'siteurl' => Functions::site_url(),
838
		);
839
	}
840
841
	/**
842
	 * Returns what features are available. Uses the slug of the module files.
843
	 *
844
	 * @return array
845
	 */
846 View Code Duplication
	public function features_available() {
847
		$raw_modules = Jetpack::get_available_modules();
848
		$modules     = array();
849
		foreach ( $raw_modules as $module ) {
850
			$modules[] = Jetpack::get_module_slug( $module );
851
		}
852
853
		return $modules;
854
	}
855
856
	/**
857
	 * Returns what features are enabled. Uses the slug of the modules files.
858
	 *
859
	 * @return array
860
	 */
861 View Code Duplication
	public function features_enabled() {
862
		$raw_modules = Jetpack::get_active_modules();
863
		$modules     = array();
864
		foreach ( $raw_modules as $module ) {
865
			$modules[] = Jetpack::get_module_slug( $module );
866
		}
867
868
		return $modules;
869
	}
870
871
	/**
872
	 * Updates the attachment parent object.
873
	 *
874
	 * @param array $args attachment and parent identifiers.
875
	 */
876
	public function update_attachment_parent( $args ) {
877
		$attachment_id = (int) $args[0];
878
		$parent_id     = (int) $args[1];
879
880
		return wp_update_post(
881
			array(
882
				'ID'          => $attachment_id,
883
				'post_parent' => $parent_id,
884
			)
885
		);
886
	}
887
888
	/**
889
	 * Serve a JSON API request.
890
	 *
891
	 * @param array $args request arguments.
892
	 */
893
	public function json_api( $args = array() ) {
894
		$json_api_args        = $args[0];
895
		$verify_api_user_args = $args[1];
896
897
		$method       = (string) $json_api_args[0];
898
		$url          = (string) $json_api_args[1];
899
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
900
		$user_details = (array) $json_api_args[4];
901
		$locale       = (string) $json_api_args[5];
902
903
		if ( ! $verify_api_user_args ) {
904
			$user_id = 0;
905
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
906
			$user_id = (int) $verify_api_user_args[1];
907
			if ( $user_id ) {
908
				$user = get_user_by( 'id', $user_id );
909
				if ( ! $user || is_wp_error( $user ) ) {
910
					return false;
911
				}
912
			}
913
		} else {
914
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
915
			if ( ! $user_id ) {
916
				return false;
917
			}
918
		}
919
920
		/* phpcs:ignore
921
		 debugging
922
		error_log( "-- begin json api via jetpack debugging -- " );
923
		error_log( "METHOD: $method" );
924
		error_log( "URL: $url" );
925
		error_log( "POST BODY: $post_body" );
926
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
927
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
928
		error_log( "-- end json api via jetpack debugging -- " );
929
		*/
930
931
		if ( 'en' !== $locale ) {
932
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
933
			$new_locale = $locale;
934
			if ( strpos( $locale, '-' ) !== false ) {
935
				$locale_pieces = explode( '-', $locale );
936
				$new_locale    = $locale_pieces[0];
937
				$new_locale   .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
938
			} else {
939
				// .com might pass 'fr' because thats what our language files are named as, where core seems
940
				// to do fr_FR - so try that if we don't think we can load the file.
941
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
942
					$new_locale = $locale . '_' . strtoupper( $locale );
943
				}
944
			}
945
946
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
947
				unload_textdomain( 'default' );
948
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
949
			}
950
		}
951
952
		$old_user = wp_get_current_user();
953
		wp_set_current_user( $user_id );
954
955
		if ( $user_id ) {
956
			$token_key = false;
957
		} else {
958
			$verified  = $this->connection->verify_xml_rpc_signature();
959
			$token_key = $verified['token_key'];
960
		}
961
962
		$token = $this->connection->get_access_token( $user_id, $token_key );
963
		if ( ! $token || is_wp_error( $token ) ) {
964
			return false;
965
		}
966
967
		define( 'REST_API_REQUEST', true );
968
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
969
970
		// needed?
971
		require_once ABSPATH . 'wp-admin/includes/admin.php';
972
973
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
974
		$api                        = WPCOM_JSON_API::init( $method, $url, $post_body );
975
		$api->token_details['user'] = $user_details;
976
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
977
978
		$display_errors = ini_set( 'display_errors', 0 ); // phpcs:ignore WordPress.PHP.IniSet
979
		ob_start();
980
		$api->serve( false );
981
		$output = ob_get_clean();
982
		ini_set( 'display_errors', $display_errors ); // phpcs:ignore WordPress.PHP.IniSet
983
984
		$nonce = wp_generate_password( 10, false );
985
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
986
987
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
988
989
		return array(
990
			(string) $output,
991
			(string) $nonce,
992
			(string) $hmac,
993
		);
994
	}
995
996
	/**
997
	 * Handles authorization actions after connecting a site, such as enabling modules.
998
	 *
999
	 * This do_post_authorization() is used in this class, as opposed to calling
1000
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
1001
	 *
1002
	 * @return void
1003
	 */
1004
	public function do_post_authorization() {
1005
		/** This filter is documented in class.jetpack-cli.php */
1006
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
1007
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
1008
	}
1009
}
1010