Completed
Push — update/xmlrpc_methods_visibili... ( 4deffa...c66ca4 )
by
unknown
453:46 queued 443:50
created

Jetpack_XMLRPC_Server   F

Complexity

Total Complexity 95

Size/Duplication

Total Lines 848
Duplicated Lines 1.3 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 95
lcom 1
cbo 12
dl 11
loc 848
rs 1.752
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
B test_api_user_code() 0 49 9
A xmlrpc_methods() 0 49 3
A bootstrap_xmlrpc_methods() 0 6 1
A authorize_xmlrpc_methods() 0 5 1
A provision_xmlrpc_methods() 0 8 1
B get_user() 0 48 6
B remote_authorize() 0 57 9
C remote_register() 0 79 12
B remote_provision() 11 58 8
B remote_connect() 0 67 9
A fetch_and_verify_local_user() 0 17 2
A get_user_by_anything() 0 13 3
A verify_action() 0 13 5
A login() 0 19 4
B error() 0 23 6
A test_connection() 0 8 1
A unlink_user() 0 19 2
A sync_object() 0 4 1
A validate_urls_for_idc_mitigation() 0 6 1
A update_attachment_parent() 0 11 1
A do_post_authorization() 0 5 1
A disconnect_blog() 0 7 2
A features_available() 0 7 2
A features_enabled() 0 7 2
A json_api() 0 7 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_XMLRPC_Server often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_XMLRPC_Server, and based on these observations, apply Extract Interface, too.

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\Connection\Secrets;
11
use Automattic\Jetpack\Connection\Tokens;
12
use Automattic\Jetpack\Roles;
13
use Automattic\Jetpack\Sync\Functions;
14
use Automattic\Jetpack\Sync\Sender;
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 connection manager object.
36
	 *
37
	 * @var Automattic\Jetpack\Connection\Manager
38
	 */
39
	private $connection;
40
41
	/**
42
	 * Creates a new XMLRPC server object.
43
	 */
44
	public function __construct() {
45
		$this->connection = new Connection_Manager();
46
	}
47
48
	/**
49
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
50
	 * user is not authenticated (->login()) then the methods are never added,
51
	 * so they will get a "does not exist" error.
52
	 *
53
	 * @param array $core_methods Core XMLRPC methods.
54
	 */
55
	public function xmlrpc_methods( $core_methods ) {
56
		$jetpack_methods = array(
57
			'jetpack.verifyAction'     => array( $this, 'verify_action' ),
58
			'jetpack.idcUrlValidation' => array( $this, 'validate_urls_for_idc_mitigation' ),
59
			'jetpack.unlinkUser'       => array( $this, 'unlink_user' ),
60
			'jetpack.testConnection'   => array( $this, 'test_connection' ),
61
		);
62
63
		$jetpack_methods = array_merge( $jetpack_methods, $this->provision_xmlrpc_methods() );
64
65
		$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...
66
67
		if ( $this->user ) {
68
			$jetpack_methods = array_merge(
69
				$jetpack_methods,
70
				array(
71
					'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),
72
				)
73
			);
74
75
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
76
				$jetpack_methods['metaWeblog.newMediaObject']      = $core_methods['metaWeblog.newMediaObject'];
77
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
78
			}
79
80
			/**
81
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
82
			 *
83
			 * @since 1.1.0
84
			 *
85
			 * @param array    $jetpack_methods XML-RPC methods available to the Jetpack Server.
86
			 * @param array    $core_methods    Available core XML-RPC methods.
87
			 * @param \WP_User $user            Information about the user authenticated in the request.
88
			 */
89
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $core_methods.

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...
90
		}
91
92
		/**
93
		 * Filters the XML-RPC methods available to Jetpack for requests signed both with a blog token or a user token.
94
		 *
95
		 * @since 3.0.0
96
		 * @since 9.6.0 Introduced the $user parameter.
97
		 *
98
		 * @param array         $jetpack_methods XML-RPC methods available to the Jetpack Server.
99
		 * @param array         $core_methods    Available core XML-RPC methods.
100
		 * @param \WP_User|bool $user            Information about the user authenticated in the request. False if authenticated with blog token.
101
		 */
102
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods, $this->user );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $core_methods.

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...
103
	}
104
105
	/**
106
	 * Whitelist of the bootstrap XML-RPC methods
107
	 */
108
	public function bootstrap_xmlrpc_methods() {
109
		return array(
110
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
111
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
112
		);
113
	}
114
115
	/**
116
	 * Additional method needed for authorization calls.
117
	 */
118
	public function authorize_xmlrpc_methods() {
119
		return array(
120
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
121
		);
122
	}
123
124
	/**
125
	 * Remote provisioning methods.
126
	 */
127
	public function provision_xmlrpc_methods() {
128
		return array(
129
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
130
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
131
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
132
			'jetpack.getUser'         => array( $this, 'get_user' ),
133
		);
134
	}
135
136
	/**
137
	 * Used to verify whether a local user exists and what role they have.
138
	 *
139
	 * @param int|string|array $request One of:
140
	 *                         int|string The local User's ID, username, or email address.
141
	 *                         array      A request array containing:
142
	 *                                    0: int|string The local User's ID, username, or email address.
143
	 *
144
	 * @return array|\IXR_Error Information about the user, or error if no such user found:
145
	 *                          roles:     string[] The user's rols.
146
	 *                          login:     string   The user's username.
147
	 *                          email_hash string[] The MD5 hash of the user's normalized email address.
148
	 *                          caps       string[] The user's capabilities.
149
	 *                          allcaps    string[] The user's granular capabilities, merged from role capabilities.
150
	 *                          token_key  string   The Token Key of the user's Jetpack token. Empty string if none.
151
	 */
152
	public function get_user( $request ) {
153
		$user_id = is_array( $request ) ? $request[0] : $request;
154
155
		if ( ! $user_id ) {
156
			return $this->error(
157
				new \WP_Error(
158
					'invalid_user',
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
159
					__( 'Invalid user identifier.', 'jetpack' ),
160
					400
161
				),
162
				'get_user'
163
			);
164
		}
165
166
		$user = $this->get_user_by_anything( $user_id );
167
168
		if ( ! $user ) {
169
			return $this->error(
170
				new \WP_Error(
171
					'user_unknown',
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
172
					__( 'User not found.', 'jetpack' ),
173
					404
174
				),
175
				'get_user'
176
			);
177
		}
178
179
		$user_token = ( new Tokens() )->get_access_token( $user->ID );
180
181
		if ( $user_token ) {
182
			list( $user_token_key ) = explode( '.', $user_token->secret );
183
			if ( $user_token_key === $user_token->secret ) {
184
				$user_token_key = '';
185
			}
186
		} else {
187
			$user_token_key = '';
188
		}
189
190
		return array(
191
			'id'         => $user->ID,
192
			'login'      => $user->user_login,
193
			'email_hash' => md5( strtolower( trim( $user->user_email ) ) ),
194
			'roles'      => $user->roles,
195
			'caps'       => $user->caps,
196
			'allcaps'    => $user->allcaps,
197
			'token_key'  => $user_token_key,
198
		);
199
	}
200
201
	/**
202
	 * Remote authorization XMLRPC method handler.
203
	 *
204
	 * @param array $request the request.
205
	 */
206
	public function remote_authorize( $request ) {
207
		$user = get_user_by( 'id', $request['state'] );
208
209
		/**
210
		 * Happens on various request handling events in the Jetpack XMLRPC server.
211
		 * The action combines several types of events:
212
		 *    - remote_authorize
213
		 *    - remote_provision
214
		 *    - get_user.
215
		 *
216
		 * @since 8.0.0
217
		 *
218
		 * @param String  $action the action name, i.e., 'remote_authorize'.
219
		 * @param String  $stage  the execution stage, can be 'begin', 'success', 'error', etc.
220
		 * @param array   $parameters extra parameters from the event.
221
		 * @param WP_User $user the acting user.
222
		 */
223
		do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'begin', array(), $user );
224
225
		foreach ( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
226
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
227
				return $this->error(
228
					new \WP_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ),
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
229
					'remote_authorize'
230
				);
231
			}
232
		}
233
234
		if ( ! $user ) {
235
			return $this->error( new \WP_Error( 'user_unknown', 'User not found.', 404 ), 'remote_authorize' );
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
236
		}
237
238
		if ( $this->connection->has_connected_owner() && $this->connection->is_user_connected( $request['state'] ) ) {
239
			return $this->error( new \WP_Error( 'already_connected', 'User already connected.', 400 ), 'remote_authorize' );
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...
240
		}
241
242
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
243
244
		if ( is_a( $verified, 'IXR_Error' ) ) {
245
			return $this->error( $verified, 'remote_authorize' );
246
		}
247
248
		wp_set_current_user( $request['state'] );
249
250
		$result = $this->connection->authorize( $request );
251
252
		if ( is_wp_error( $result ) ) {
253
			return $this->error( $result, 'remote_authorize' );
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->connection->authorize($request) on line 250 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...
254
		}
255
256
		// This action is documented in class.jetpack-xmlrpc-server.php.
257
		do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'success' );
258
259
		return array(
260
			'result' => $result,
261
		);
262
	}
263
264
	/**
265
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
266
	 * register this site so that a plan can be provisioned.
267
	 *
268
	 * @param array $request An array containing at minimum nonce and local_user keys.
269
	 *
270
	 * @return \WP_Error|array
271
	 */
272
	public function remote_register( $request ) {
273
		// This action is documented in class.jetpack-xmlrpc-server.php.
274
		do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'begin', array() );
275
276
		$user = $this->fetch_and_verify_local_user( $request );
277
278
		if ( ! $user ) {
279
			return $this->error(
280
				new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ),
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...
281
				'remote_register'
282
			);
283
		}
284
285
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
286
			return $this->error( $user, 'remote_register' );
287
		}
288
289
		if ( empty( $request['nonce'] ) ) {
290
			return $this->error(
291
				new \WP_Error(
292
					'nonce_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
293
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
294
					400
295
				),
296
				'remote_register'
297
			);
298
		}
299
300
		$nonce = sanitize_text_field( $request['nonce'] );
301
		unset( $request['nonce'] );
302
303
		$api_url  = $this->connection->api_url( 'partner_provision_nonce_check' );
304
		$response = Client::_wp_remote_request(
305
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
306
			array( 'method' => 'GET' ),
307
			true
308
		);
309
310
		if (
311
			200 !== wp_remote_retrieve_response_code( $response ) ||
312
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
313
		) {
314
			return $this->error(
315
				new \WP_Error(
316
					'invalid_nonce',
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
317
					__( 'There was an issue validating this request.', 'jetpack' ),
318
					400
319
				),
320
				'remote_register'
321
			);
322
		}
323
324
		if ( ! Jetpack_Options::get_option( 'id' ) || ! ( new Tokens() )->get_access_token() || ! empty( $request['force'] ) ) {
325
			wp_set_current_user( $user->ID );
326
327
			// This code mostly copied from Jetpack::admin_page_load.
328
			Jetpack::maybe_set_version_option();
329
			$registered = Jetpack::try_registration();
330
			if ( is_wp_error( $registered ) ) {
331
				return $this->error( $registered, 'remote_register' );
332
			} elseif ( ! $registered ) {
333
				return $this->error(
334
					new \WP_Error(
335
						'registration_error',
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
336
						__( 'There was an unspecified error registering the site', 'jetpack' ),
337
						400
338
					),
339
					'remote_register'
340
				);
341
			}
342
		}
343
344
		// This action is documented in class.jetpack-xmlrpc-server.php.
345
		do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'success' );
346
347
		return array(
348
			'client_id' => Jetpack_Options::get_option( 'id' ),
349
		);
350
	}
351
352
	/**
353
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
354
	 * register this site so that a plan can be provisioned.
355
	 *
356
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
357
	 *
358
	 * @return \WP_Error|array
359
	 */
360
	public function remote_provision( $request ) {
361
		$user = $this->fetch_and_verify_local_user( $request );
362
363
		if ( ! $user ) {
364
			return $this->error(
365
				new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ),
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...
366
				'remote_provision'
367
			);
368
		}
369
370
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
371
			return $this->error( $user, 'remote_provision' );
372
		}
373
374
		$site_icon = get_site_icon_url();
375
376
		$auto_enable_sso = ( ! $this->connection->has_connected_owner() || Jetpack::is_module_active( 'sso' ) );
377
378
		/** This filter is documented in class.jetpack-cli.php */
379 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
380
			$redirect_uri = add_query_arg(
381
				array(
382
					'action'      => 'jetpack-sso',
383
					'redirect_to' => rawurlencode( admin_url() ),
384
				),
385
				wp_login_url() // TODO: come back to Jetpack dashboard?
386
			);
387
		} else {
388
			$redirect_uri = admin_url();
389
		}
390
391
		// Generate secrets.
392
		$roles   = new Roles();
393
		$role    = $roles->translate_user_to_role( $user );
394
		$secrets = ( new Secrets() )->generate( 'authorize', $user->ID );
395
396
		$response = array(
397
			'jp_version'   => JETPACK__VERSION,
398
			'redirect_uri' => $redirect_uri,
399
			'user_id'      => $user->ID,
400
			'user_email'   => $user->user_email,
401
			'user_login'   => $user->user_login,
402
			'scope'        => $this->connection->sign_role( $role, $user->ID ),
403
			'secret'       => $secrets['secret_1'],
404
			'is_active'    => $this->connection->has_connected_owner(),
405
		);
406
407
		if ( $site_icon ) {
408
			$response['site_icon'] = $site_icon;
409
		}
410
411
		if ( ! empty( $request['onboarding'] ) ) {
412
			Jetpack::create_onboarding_token();
413
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
414
		}
415
416
		return $response;
417
	}
418
419
	/**
420
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
421
	 * an access token for the given user.
422
	 *
423
	 * @param array       $request    An array containing local_user and nonce keys at minimum.
424
	 * @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...
425
	 * @return mixed
426
	 */
427
	public function remote_connect( $request, $ixr_client = false ) {
428
		if ( $this->connection->has_connected_owner() ) {
429
			return $this->error(
430
				new WP_Error(
431
					'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...
432
					__( 'Jetpack is already connected.', 'jetpack' ),
433
					400
434
				),
435
				'remote_connect'
436
			);
437
		}
438
439
		$user = $this->fetch_and_verify_local_user( $request );
440
441
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
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
					__( 'Valid user is required.', 'jetpack' ),
446
					400
447
				),
448
				'remote_connect'
449
			);
450
		}
451
452
		if ( empty( $request['nonce'] ) ) {
453
			return $this->error(
454
				new WP_Error(
455
					'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...
456
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
457
					400
458
				),
459
				'remote_connect'
460
			);
461
		}
462
463
		if ( ! $ixr_client ) {
464
			$ixr_client = new Jetpack_IXR_Client();
465
		}
466
		// TODO: move this query into the Tokens class?
467
		$ixr_client->query(
468
			'jetpack.getUserAccessToken',
469
			array(
470
				'nonce'            => sanitize_text_field( $request['nonce'] ),
471
				'external_user_id' => $user->ID,
472
			)
473
		);
474
475
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
476
		if ( empty( $token ) ) {
477
			return $this->error(
478
				new WP_Error(
479
					'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...
480
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
481
					400
482
				),
483
				'remote_connect'
484
			);
485
		}
486
		$token = sanitize_text_field( $token );
487
488
		( new Tokens() )->update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
489
490
		$this->do_post_authorization();
491
492
		return $this->connection->has_connected_owner();
493
	}
494
495
	/**
496
	 * Getter for the local user to act as.
497
	 *
498
	 * @param array $request the current request data.
499
	 */
500
	private function fetch_and_verify_local_user( $request ) {
501
		if ( empty( $request['local_user'] ) ) {
502
			return $this->error(
503
				new \WP_Error(
504
					'local_user_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
505
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
506
					400
507
				),
508
				'remote_provision'
509
			);
510
		}
511
512
		// Local user is used to look up by login, email or ID.
513
		$local_user_info = $request['local_user'];
514
515
		return $this->get_user_by_anything( $local_user_info );
516
	}
517
518
	/**
519
	 * Gets the user object by its data.
520
	 *
521
	 * @param string $user_id can be any identifying user data.
522
	 */
523
	private function get_user_by_anything( $user_id ) {
524
		$user = get_user_by( 'login', $user_id );
525
526
		if ( ! $user ) {
527
			$user = get_user_by( 'email', $user_id );
528
		}
529
530
		if ( ! $user ) {
531
			$user = get_user_by( 'ID', $user_id );
532
		}
533
534
		return $user;
535
	}
536
537
	/**
538
	 * Possible error_codes:
539
	 *
540
	 * - verify_secret_1_missing
541
	 * - verify_secret_1_malformed
542
	 * - verify_secrets_missing: verification secrets are not found in database
543
	 * - verify_secrets_incomplete: verification secrets are only partially found in database
544
	 * - verify_secrets_expired: verification secrets have expired
545
	 * - verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
546
	 * - state_missing: required parameter of state not found
547
	 * - state_malformed: state is not a digit
548
	 * - invalid_state: state in request does not match the stored state
549
	 *
550
	 * The 'authorize' and 'register' actions have additional error codes
551
	 *
552
	 * state_missing: a state ( user id ) was not supplied
553
	 * state_malformed: state is not the correct data type
554
	 * invalid_state: supplied state does not match the stored state
555
	 *
556
	 * @param array $params action An array of 3 parameters:
557
	 *     [0]: string action. Possible values are `authorize`, `publicize` and `register`.
558
	 *     [1]: string secret_1.
559
	 *     [2]: int state.
560
	 * @return \IXR_Error|string IXR_Error on failure, secret_2 on success.
561
	 */
562
	public function verify_action( $params ) {
563
		$action        = isset( $params[0] ) ? $params[0] : '';
564
		$verify_secret = isset( $params[1] ) ? $params[1] : '';
565
		$state         = isset( $params[2] ) ? $params[2] : '';
566
567
		$result = ( new Secrets() )->verify( $action, $verify_secret, $state );
568
569
		if ( is_wp_error( $result ) ) {
570
			return $this->error( $result );
0 ignored issues
show
Bug introduced by
It seems like $result defined by (new \Automattic\Jetpack...$verify_secret, $state) on line 567 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...
571
		}
572
573
		return $result;
574
	}
575
576
	/**
577
	 * Wrapper for wp_authenticate( $username, $password );
578
	 *
579
	 * @return \WP_User|bool
580
	 */
581
	public function login() {
582
		$this->connection->require_jetpack_authentication();
583
		$user = wp_authenticate( 'username', 'password' );
584
		if ( is_wp_error( $user ) ) {
585
			if ( 'authentication_failed' === $user->get_error_code() ) { // Generic error could mean most anything.
586
				$this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
587
			} else {
588
				$this->error = $user;
589
			}
590
			return false;
591
		} elseif ( ! $user ) { // Shouldn't happen.
592
			$this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_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...
593
			return false;
594
		}
595
596
		wp_set_current_user( $user->ID );
597
598
		return $user;
599
	}
600
601
	/**
602
	 * Returns the current error as an \IXR_Error
603
	 *
604
	 * @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...
605
	 * @param string               $event_name The event name.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $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...
606
	 * @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...
607
	 * @return bool|\IXR_Error
608
	 */
609
	public function error( $error = null, $event_name = null, $user = null ) {
610
		if ( null !== $event_name ) {
611
			// This action is documented in class.jetpack-xmlrpc-server.php.
612
			do_action( 'jetpack_xmlrpc_server_event', $event_name, 'fail', $error, $user );
613
		}
614
615
		if ( ! is_null( $error ) ) {
616
			$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...
617
		}
618
619
		if ( is_wp_error( $this->error ) ) {
620
			$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...
621
			if ( ! $code ) {
622
				$code = -10520;
623
			}
624
			$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...
625
			return new \IXR_Error( $code, $message );
626
		} elseif ( is_a( $this->error, 'IXR_Error' ) ) {
627
			return $this->error;
628
		}
629
630
		return false;
631
	}
632
633
	/* API Methods */
634
635
	/**
636
	 * Just authenticates with the given Jetpack credentials.
637
	 *
638
	 * @return string A success string. The Jetpack plugin filters it and make it return the Jetpack plugin version.
639
	 */
640
	public function test_connection() {
641
		/**
642
		 * Filters the successful response of the XMLRPC test_connection method
643
		 *
644
		 * @param string $response The response string.
645
		 */
646
		return apply_filters( 'jetpack_xmlrpc_test_connection_response', 'success' );
647
	}
648
649
	/**
650
	 * Test the API user code.
651
	 *
652
	 * @param array $args arguments identifying the test site.
653
	 */
654
	public function test_api_user_code( $args ) {
655
		$client_id = (int) $args[0];
656
		$user_id   = (int) $args[1];
657
		$nonce     = (string) $args[2];
658
		$verify    = (string) $args[3];
659
660
		if ( ! $client_id || ! $user_id || ! strlen( $nonce ) || 32 !== strlen( $verify ) ) {
661
			return false;
662
		}
663
664
		$user = get_user_by( 'id', $user_id );
665
		if ( ! $user || is_wp_error( $user ) ) {
666
			return false;
667
		}
668
669
		/* phpcs:ignore
670
		 debugging
671
		error_log( "CLIENT: $client_id" );
672
		error_log( "USER:   $user_id" );
673
		error_log( "NONCE:  $nonce" );
674
		error_log( "VERIFY: $verify" );
675
		*/
676
677
		$jetpack_token = ( new Tokens() )->get_access_token( $user_id );
678
679
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
680
		if ( ! $api_user_code ) {
681
			return false;
682
		}
683
684
		$hmac = hash_hmac(
685
			'md5',
686
			json_encode( // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
687
				(object) array(
688
					'client_id' => (int) $client_id,
689
					'user_id'   => (int) $user_id,
690
					'nonce'     => (string) $nonce,
691
					'code'      => (string) $api_user_code,
692
				)
693
			),
694
			$jetpack_token->secret
695
		);
696
697
		if ( ! hash_equals( $hmac, $verify ) ) {
698
			return false;
699
		}
700
701
		return $user_id;
702
	}
703
704
	/**
705
	 * Unlink a user from WordPress.com
706
	 *
707
	 * When the request is done without any parameter, this XMLRPC callback gets an empty array as input.
708
	 *
709
	 * If $user_id is not provided, it will try to disconnect the current logged in user. This will fail if called by the Master User.
710
	 *
711
	 * If $user_id is is provided, it will try to disconnect the informed user, even if it's the Master User.
712
	 *
713
	 * @param mixed $user_id The user ID to disconnect from this site.
714
	 */
715
	public function unlink_user( $user_id = array() ) {
716
		$user_id = (int) $user_id;
717
		if ( $user_id < 1 ) {
718
			$user_id = null;
719
		}
720
		/**
721
		 * Fired when we want to log an event to the Jetpack event log.
722
		 *
723
		 * @since 7.7.0
724
		 *
725
		 * @param string $code Unique name for the event.
726
		 * @param string $data Optional data about the event.
727
		 */
728
		do_action( 'jetpack_event_log', 'unlink' );
729
		return $this->connection->disconnect_user(
730
			$user_id,
731
			(bool) $user_id
732
		);
733
	}
734
735
	/**
736
	 * Returns any object that is able to be synced.
737
	 *
738
	 * @deprecated since 7.8.0
739
	 * @see Automattic\Jetpack\Sync\Sender::sync_object()
740
	 *
741
	 * @param array $args the synchronized object parameters.
742
	 * @return string Encoded sync object.
743
	 */
744
	public function sync_object( $args ) {
745
		_deprecated_function( __METHOD__, 'jetpack-7.8', 'Automattic\\Jetpack\\Sync\\Sender::sync_object' );
746
		return Sender::get_instance()->sync_object( $args );
747
	}
748
749
	/**
750
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
751
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
752
	 * and the remote Jetpack site.
753
	 *
754
	 * @return array
755
	 */
756
	public function validate_urls_for_idc_mitigation() {
757
		return array(
758
			'home'    => Functions::home_url(),
759
			'siteurl' => Functions::site_url(),
760
		);
761
	}
762
763
	/**
764
	 * Updates the attachment parent object.
765
	 *
766
	 * @param array $args attachment and parent identifiers.
767
	 */
768
	public function update_attachment_parent( $args ) {
769
		$attachment_id = (int) $args[0];
770
		$parent_id     = (int) $args[1];
771
772
		return wp_update_post(
773
			array(
774
				'ID'          => $attachment_id,
775
				'post_parent' => $parent_id,
776
			)
777
		);
778
	}
779
780
	/**
781
	 * Handles authorization actions after connecting a site, such as enabling modules.
782
	 *
783
	 * This do_post_authorization() is used in this class, as opposed to calling
784
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
785
	 *
786
	 * @return void
787
	 */
788
	public function do_post_authorization() {
789
		/** This filter is documented in class.jetpack-cli.php */
790
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
791
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
792
	}
793
794
	/**
795
	 * Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
796
	 *
797
	 * Disconnect this blog from the connected wordpress.com account
798
	 *
799
	 * @deprecated since 9.6.0
800
	 * @see Jetpack_XMLRPC_Methods::disconnect_blog() in the Jetpack plugin
801
	 *
802
	 * @return boolean
803
	 */
804
	public function disconnect_blog() {
805
		_deprecated_function( __METHOD__, 'jetpack-9.6', 'Jetpack_XMLRPC_Methods::disconnect_blog()' );
806
		if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
807
			return Jetpack_XMLRPC_Methods::disconnect_blog();
808
		}
809
		return false;
810
	}
811
812
	/**
813
	 * Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
814
	 *
815
	 * Returns what features are available. Uses the slug of the module files.
816
	 *
817
	 * @deprecated since 9.6.0
818
	 * @see Jetpack_XMLRPC_Methods::features_available() in the Jetpack plugin
819
	 *
820
	 * @return array
821
	 */
822
	public function features_available() {
823
		_deprecated_function( __METHOD__, 'jetpack-9.6', 'Jetpack_XMLRPC_Methods::features_available()' );
824
		if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
825
			return Jetpack_XMLRPC_Methods::features_available();
826
		}
827
		return array();
828
	}
829
830
	/**
831
	 * Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
832
	 *
833
	 * Returns what features are enabled. Uses the slug of the modules files.
834
	 *
835
	 * @deprecated since 9.6.0
836
	 * @see Jetpack_XMLRPC_Methods::features_enabled() in the Jetpack plugin
837
	 *
838
	 * @return array
839
	 */
840
	public function features_enabled() {
841
		_deprecated_function( __METHOD__, 'jetpack-9.6', 'Jetpack_XMLRPC_Methods::features_enabled()' );
842
		if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
843
			return Jetpack_XMLRPC_Methods::features_enabled();
844
		}
845
		return array();
846
	}
847
848
	/**
849
	 * Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
850
	 *
851
	 * Serve a JSON API request.
852
	 *
853
	 * @deprecated since 9.6.0
854
	 * @see Jetpack_XMLRPC_Methods::json_api() in the Jetpack plugin
855
	 *
856
	 * @param array $args request arguments.
857
	 */
858
	public function json_api( $args = array() ) {
859
		_deprecated_function( __METHOD__, 'jetpack-9.6', 'Jetpack_XMLRPC_Methods::json_api()' );
860
		if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
861
			return Jetpack_XMLRPC_Methods::json_api( $args );
862
		}
863
		return array();
864
	}
865
866
}
867