Completed
Push — renovate/url-polyfill-1.x ( 01368d...f2aaf6 )
by
unknown
114:05 queued 106:27
created

Jetpack_XMLRPC_Server::xmlrpc_methods()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 10
nop 1
dl 0
loc 58
rs 8.6052
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Utils as Connection_Utils;
11
use Automattic\Jetpack\Roles;
12
use Automattic\Jetpack\Sync\Modules;
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
	 * @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 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...
45
	 */
46
	public function __construct( $manager = null ) {
47
		$this->connection = is_null( $manager ) ? new Connection_Manager() : $manager;
48
	}
49
50
	/**
51
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
52
	 * user is not authenticated (->login()) then the methods are never added,
53
	 * so they will get a "does not exist" error.
54
	 *
55
	 * @param array $core_methods Core XMLRPC methods.
56
	 */
57
	public function xmlrpc_methods( $core_methods ) {
58
		$jetpack_methods = array(
59
			'jetpack.verifyAction'    => array( $this, 'verify_action' ),
60
			'jetpack.getUser'         => array( $this, 'get_user' ),
61
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
62
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
63
		);
64
65
		if ( class_exists( 'Jetpack' ) ) {
66
			$jetpack_methods['jetpack.jsonAPI'] = array( $this, 'json_api' );
67
		}
68
69
		$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...
70
71
		if ( $this->user ) {
72
			$jetpack_methods = array_merge(
73
				$jetpack_methods,
74
				array(
75
					'jetpack.testAPIUserCode'  => array( $this, 'test_api_user_code' ),
76
					'jetpack.disconnectBlog'   => array( $this, 'disconnect_blog' ),
77
					'jetpack.unlinkUser'       => array( $this, 'unlink_user' ),
78
					'jetpack.idcUrlValidation' => array( $this, 'validate_urls_for_idc_mitigation' ),
79
				)
80
			);
81
82
			if ( class_exists( 'Jetpack' ) ) {
83
				$jetpack_methods['jetpack.testConnection']    = array( $this, 'test_connection' );
84
				$jetpack_methods['jetpack.featuresAvailable'] = array( $this, 'features_available' );
85
				$jetpack_methods['jetpack.featuresEnabled']   = array( $this, 'features_enabled' );
86
			}
87
88
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
89
				$jetpack_methods['metaWeblog.newMediaObject']      = $core_methods['metaWeblog.newMediaObject'];
90
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
91
			}
92
93
			/**
94
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
95
			 *
96
			 * @since 1.1.0
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 $user            Information about a given WordPress user.
101
			 */
102
			$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...
103
		}
104
105
		/**
106
		 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
107
		 *
108
		 * @since 3.0.0
109
		 *
110
		 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
111
		 * @param array $core_methods    Available core XML-RPC methods.
112
		 */
113
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
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...
114
	}
115
116
	/**
117
	 * Whitelist of the bootstrap XML-RPC methods
118
	 */
119
	public function bootstrap_xmlrpc_methods() {
120
		return array(
121
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
122
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
123
		);
124
	}
125
126
	/**
127
	 * Additional method needed for authorization calls.
128
	 */
129
	public function authorize_xmlrpc_methods() {
130
		return array(
131
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
132
		);
133
	}
134
135
	/**
136
	 * Remote provisioning methods.
137
	 */
138
	public function provision_xmlrpc_methods() {
139
		return array(
140
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
141
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
142
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
143
			'jetpack.getUser'         => array( $this, 'get_user' ),
144
		);
145
	}
146
147
	/**
148
	 * Used to verify whether a local user exists and what role they have.
149
	 *
150
	 * @param int|string|array $request One of:
151
	 *                         int|string The local User's ID, username, or email address.
152
	 *                         array      A request array containing:
153
	 *                                    0: int|string The local User's ID, username, or email address.
154
	 *
155
	 * @return array|\IXR_Error Information about the user, or error if no such user found:
156
	 *                          roles:     string[] The user's rols.
157
	 *                          login:     string   The user's username.
158
	 *                          email_hash string[] The MD5 hash of the user's normalized email address.
159
	 *                          caps       string[] The user's capabilities.
160
	 *                          allcaps    string[] The user's granular capabilities, merged from role capabilities.
161
	 *                          token_key  string   The Token Key of the user's Jetpack token. Empty string if none.
162
	 */
163
	public function get_user( $request ) {
164
		$user_id = is_array( $request ) ? $request[0] : $request;
165
166
		if ( ! $user_id ) {
167
			return $this->error(
168
				new \WP_Error(
169
					'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...
170
					__( 'Invalid user identifier.', 'jetpack' ),
171
					400
172
				),
173
				'get_user'
174
			);
175
		}
176
177
		$user = $this->get_user_by_anything( $user_id );
178
179
		if ( ! $user ) {
180
			return $this->error(
181
				new \WP_Error(
182
					'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...
183
					__( 'User not found.', 'jetpack' ),
184
					404
185
				),
186
				'get_user'
187
			);
188
		}
189
190
		$user_token = $this->connection->get_access_token( $user->ID );
191
192
		if ( $user_token ) {
193
			list( $user_token_key ) = explode( '.', $user_token->secret );
194
			if ( $user_token_key === $user_token->secret ) {
195
				$user_token_key = '';
196
			}
197
		} else {
198
			$user_token_key = '';
199
		}
200
201
		return array(
202
			'id'         => $user->ID,
203
			'login'      => $user->user_login,
204
			'email_hash' => md5( strtolower( trim( $user->user_email ) ) ),
205
			'roles'      => $user->roles,
206
			'caps'       => $user->caps,
207
			'allcaps'    => $user->allcaps,
208
			'token_key'  => $user_token_key,
209
		);
210
	}
211
212
	/**
213
	 * Remote authorization XMLRPC method handler.
214
	 *
215
	 * @param array $request the request.
216
	 */
217
	public function remote_authorize( $request ) {
218
		$user = get_user_by( 'id', $request['state'] );
219
220
		/**
221
		 * Happens on various request handling events in the Jetpack XMLRPC server.
222
		 * The action combines several types of events:
223
		 *    - remote_authorize
224
		 *    - remote_provision
225
		 *    - get_user.
226
		 *
227
		 * @since 8.0.0
228
		 *
229
		 * @param String  $action the action name, i.e., 'remote_authorize'.
230
		 * @param String  $stage  the execution stage, can be 'begin', 'success', 'error', etc.
231
		 * @param array   $parameters extra parameters from the event.
232
		 * @param WP_User $user the acting user.
233
		 */
234
		do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'begin', array(), $user );
235
236
		foreach ( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
237
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
238
				return $this->error(
239
					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...
240
					'remote_authorize'
241
				);
242
			}
243
		}
244
245
		if ( ! $user ) {
246
			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...
247
		}
248
249
		if ( $this->connection->is_active() && $this->connection->is_user_connected( $request['state'] ) ) {
250
			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...
251
		}
252
253
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
254
255
		if ( is_a( $verified, 'IXR_Error' ) ) {
256
			return $this->error( $verified, 'remote_authorize' );
257
		}
258
259
		wp_set_current_user( $request['state'] );
260
261
		$result = $this->connection->authorize( $request );
262
263
		if ( is_wp_error( $result ) ) {
264
			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 261 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...
265
		}
266
267
		// This action is documented in class.jetpack-xmlrpc-server.php.
268
		do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'success' );
269
270
		return array(
271
			'result' => $result,
272
		);
273
	}
274
275
	/**
276
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
277
	 * register this site so that a plan can be provisioned.
278
	 *
279
	 * @param array $request An array containing at minimum nonce and local_user keys.
280
	 *
281
	 * @return \WP_Error|array
282
	 */
283
	public function remote_register( $request ) {
284
		// This action is documented in class.jetpack-xmlrpc-server.php.
285
		do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'begin', array() );
286
287
		$user = $this->fetch_and_verify_local_user( $request );
288
289
		if ( ! $user ) {
290
			return $this->error(
291
				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...
292
				'remote_register'
293
			);
294
		}
295
296
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
297
			return $this->error( $user, 'remote_register' );
298
		}
299
300
		if ( empty( $request['nonce'] ) ) {
301
			return $this->error(
302
				new \WP_Error(
303
					'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...
304
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
305
					400
306
				),
307
				'remote_register'
308
			);
309
		}
310
311
		$nonce = sanitize_text_field( $request['nonce'] );
312
		unset( $request['nonce'] );
313
314
		$api_url  = Connection_Utils::fix_url_for_bad_hosts(
315
			$this->connection->api_url( 'partner_provision_nonce_check' )
316
		);
317
		$response = Client::_wp_remote_request(
318
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
319
			array( 'method' => 'GET' ),
320
			true
321
		);
322
323
		if (
324
			200 !== wp_remote_retrieve_response_code( $response ) ||
325
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
326
		) {
327
			return $this->error(
328
				new \WP_Error(
329
					'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...
330
					__( 'There was an issue validating this request.', 'jetpack' ),
331
					400
332
				),
333
				'remote_register'
334
			);
335
		}
336
337
		if ( ! Jetpack_Options::get_option( 'id' ) || ! $this->connection->get_access_token() || ! empty( $request['force'] ) ) {
338
			wp_set_current_user( $user->ID );
339
340
			// This code mostly copied from Jetpack::admin_page_load.
341
			Jetpack::maybe_set_version_option();
342
			$registered = Jetpack::try_registration();
343
			if ( is_wp_error( $registered ) ) {
344
				return $this->error( $registered, 'remote_register' );
345
			} elseif ( ! $registered ) {
346
				return $this->error(
347
					new \WP_Error(
348
						'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...
349
						__( 'There was an unspecified error registering the site', 'jetpack' ),
350
						400
351
					),
352
					'remote_register'
353
				);
354
			}
355
		}
356
357
		// This action is documented in class.jetpack-xmlrpc-server.php.
358
		do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'success' );
359
360
		return array(
361
			'client_id' => Jetpack_Options::get_option( 'id' ),
362
		);
363
	}
364
365
	/**
366
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
367
	 * register this site so that a plan can be provisioned.
368
	 *
369
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
370
	 *
371
	 * @return \WP_Error|array
372
	 */
373
	public function remote_provision( $request ) {
374
		$user = $this->fetch_and_verify_local_user( $request );
375
376
		if ( ! $user ) {
377
			return $this->error(
378
				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...
379
				'remote_provision'
380
			);
381
		}
382
383
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
384
			return $this->error( $user, 'remote_provision' );
385
		}
386
387
		$site_icon = get_site_icon_url();
388
389
		$auto_enable_sso = ( ! $this->connection->is_active() || Jetpack::is_module_active( 'sso' ) );
390
391
		/** This filter is documented in class.jetpack-cli.php */
392 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
393
			$redirect_uri = add_query_arg(
394
				array(
395
					'action'      => 'jetpack-sso',
396
					'redirect_to' => rawurlencode( admin_url() ),
397
				),
398
				wp_login_url() // TODO: come back to Jetpack dashboard?
399
			);
400
		} else {
401
			$redirect_uri = admin_url();
402
		}
403
404
		// Generate secrets.
405
		$roles   = new Roles();
406
		$role    = $roles->translate_user_to_role( $user );
407
		$secrets = $this->connection->generate_secrets( 'authorize', $user->ID );
408
409
		$response = array(
410
			'jp_version'   => JETPACK__VERSION,
411
			'redirect_uri' => $redirect_uri,
412
			'user_id'      => $user->ID,
413
			'user_email'   => $user->user_email,
414
			'user_login'   => $user->user_login,
415
			'scope'        => $this->connection->sign_role( $role, $user->ID ),
416
			'secret'       => $secrets['secret_1'],
417
			'is_active'    => $this->connection->is_active(),
418
		);
419
420
		if ( $site_icon ) {
421
			$response['site_icon'] = $site_icon;
422
		}
423
424
		if ( ! empty( $request['onboarding'] ) ) {
425
			Jetpack::create_onboarding_token();
426
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
427
		}
428
429
		return $response;
430
	}
431
432
	/**
433
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
434
	 * an access token for the given user.
435
	 *
436
	 * @param array       $request    An array containing local_user and nonce keys at minimum.
437
	 * @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...
438
	 * @return mixed
439
	 */
440
	public function remote_connect( $request, $ixr_client = false ) {
441
		if ( $this->connection->is_active() ) {
442
			return $this->error(
443
				new WP_Error(
444
					'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...
445
					__( 'Jetpack is already connected.', 'jetpack' ),
446
					400
447
				),
448
				'remote_connect'
449
			);
450
		}
451
452
		$user = $this->fetch_and_verify_local_user( $request );
453
454
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
455
			return $this->error(
456
				new WP_Error(
457
					'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...
458
					__( 'Valid user is required.', 'jetpack' ),
459
					400
460
				),
461
				'remote_connect'
462
			);
463
		}
464
465
		if ( empty( $request['nonce'] ) ) {
466
			return $this->error(
467
				new WP_Error(
468
					'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...
469
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
470
					400
471
				),
472
				'remote_connect'
473
			);
474
		}
475
476
		if ( ! $ixr_client ) {
477
			$ixr_client = new Jetpack_IXR_Client();
478
		}
479
		$ixr_client->query(
480
			'jetpack.getUserAccessToken',
481
			array(
482
				'nonce'            => sanitize_text_field( $request['nonce'] ),
483
				'external_user_id' => $user->ID,
484
			)
485
		);
486
487
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
488
		if ( empty( $token ) ) {
489
			return $this->error(
490
				new WP_Error(
491
					'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...
492
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
493
					400
494
				),
495
				'remote_connect'
496
			);
497
		}
498
		$token = sanitize_text_field( $token );
499
500
		Connection_Utils::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
501
502
		$this->do_post_authorization();
503
504
		return $this->connection->is_active();
505
	}
506
507
	/**
508
	 * Getter for the local user to act as.
509
	 *
510
	 * @param array $request the current request data.
511
	 */
512
	private function fetch_and_verify_local_user( $request ) {
513
		if ( empty( $request['local_user'] ) ) {
514
			return $this->error(
515
				new \WP_Error(
516
					'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...
517
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
518
					400
519
				),
520
				'remote_provision'
521
			);
522
		}
523
524
		// Local user is used to look up by login, email or ID.
525
		$local_user_info = $request['local_user'];
526
527
		return $this->get_user_by_anything( $local_user_info );
528
	}
529
530
	/**
531
	 * Gets the user object by its data.
532
	 *
533
	 * @param string $user_id can be any identifying user data.
534
	 */
535
	private function get_user_by_anything( $user_id ) {
536
		$user = get_user_by( 'login', $user_id );
537
538
		if ( ! $user ) {
539
			$user = get_user_by( 'email', $user_id );
540
		}
541
542
		if ( ! $user ) {
543
			$user = get_user_by( 'ID', $user_id );
544
		}
545
546
		return $user;
547
	}
548
549
	/**
550
	 * Possible error_codes:
551
	 *
552
	 * - verify_secret_1_missing
553
	 * - verify_secret_1_malformed
554
	 * - verify_secrets_missing: verification secrets are not found in database
555
	 * - verify_secrets_incomplete: verification secrets are only partially found in database
556
	 * - verify_secrets_expired: verification secrets have expired
557
	 * - verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
558
	 * - state_missing: required parameter of state not found
559
	 * - state_malformed: state is not a digit
560
	 * - invalid_state: state in request does not match the stored state
561
	 *
562
	 * The 'authorize' and 'register' actions have additional error codes
563
	 *
564
	 * state_missing: a state ( user id ) was not supplied
565
	 * state_malformed: state is not the correct data type
566
	 * invalid_state: supplied state does not match the stored state
567
	 *
568
	 * @param array $params action An array of 3 parameters:
569
	 *     [0]: string action. Possible values are `authorize`, `publicize` and `register`.
570
	 *     [1]: string secret_1.
571
	 *     [2]: int state.
572
	 * @return \IXR_Error|string IXR_Error on failure, secret_2 on success.
573
	 */
574
	public function verify_action( $params ) {
575
		$action        = isset( $params[0] ) ? $params[0] : '';
576
		$verify_secret = isset( $params[1] ) ? $params[1] : '';
577
		$state         = isset( $params[2] ) ? $params[2] : '';
578
579
		$result = $this->connection->verify_secrets( $action, $verify_secret, $state );
580
581
		if ( is_wp_error( $result ) ) {
582
			return $this->error( $result );
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->connection->verif...$verify_secret, $state) on line 579 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...
583
		}
584
585
		return $result;
586
	}
587
588
	/**
589
	 * Wrapper for wp_authenticate( $username, $password );
590
	 *
591
	 * @return \WP_User|bool
592
	 */
593
	public function login() {
594
		$this->connection->require_jetpack_authentication();
595
		$user = wp_authenticate( 'username', 'password' );
596
		if ( is_wp_error( $user ) ) {
597
			if ( 'authentication_failed' === $user->get_error_code() ) { // Generic error could mean most anything.
598
				$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...
599
			} else {
600
				$this->error = $user;
601
			}
602
			return false;
603
		} elseif ( ! $user ) { // Shouldn't happen.
604
			$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...
605
			return false;
606
		}
607
608
		return $user;
609
	}
610
611
	/**
612
	 * Returns the current error as an \IXR_Error
613
	 *
614
	 * @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...
615
	 * @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...
616
	 * @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...
617
	 * @return bool|\IXR_Error
618
	 */
619
	public function error( $error = null, $event_name = null, $user = null ) {
620
		if ( null !== $event_name ) {
621
			// This action is documented in class.jetpack-xmlrpc-server.php.
622
			do_action( 'jetpack_xmlrpc_server_event', $event_name, 'fail', $error, $user );
623
		}
624
625
		if ( ! is_null( $error ) ) {
626
			$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...
627
		}
628
629
		if ( is_wp_error( $this->error ) ) {
630
			$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...
631
			if ( ! $code ) {
632
				$code = -10520;
633
			}
634
			$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...
635
			return new \IXR_Error( $code, $message );
636
		} elseif ( is_a( $this->error, 'IXR_Error' ) ) {
637
			return $this->error;
638
		}
639
640
		return false;
641
	}
642
643
	/* API Methods */
644
645
	/**
646
	 * Just authenticates with the given Jetpack credentials.
647
	 *
648
	 * @return string The current Jetpack version number
649
	 */
650
	public function test_connection() {
651
		return JETPACK__VERSION;
652
	}
653
654
	/**
655
	 * Test the API user code.
656
	 *
657
	 * @param array $args arguments identifying the test site.
658
	 */
659
	public function test_api_user_code( $args ) {
660
		$client_id = (int) $args[0];
661
		$user_id   = (int) $args[1];
662
		$nonce     = (string) $args[2];
663
		$verify    = (string) $args[3];
664
665
		if ( ! $client_id || ! $user_id || ! strlen( $nonce ) || 32 !== strlen( $verify ) ) {
666
			return false;
667
		}
668
669
		$user = get_user_by( 'id', $user_id );
670
		if ( ! $user || is_wp_error( $user ) ) {
671
			return false;
672
		}
673
674
		/* phpcs:ignore
675
		 debugging
676
		error_log( "CLIENT: $client_id" );
677
		error_log( "USER:   $user_id" );
678
		error_log( "NONCE:  $nonce" );
679
		error_log( "VERIFY: $verify" );
680
		*/
681
682
		$jetpack_token = $this->connection->get_access_token( $user_id );
683
684
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
685
		if ( ! $api_user_code ) {
686
			return false;
687
		}
688
689
		$hmac = hash_hmac(
690
			'md5',
691
			json_encode( // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
692
				(object) array(
693
					'client_id' => (int) $client_id,
694
					'user_id'   => (int) $user_id,
695
					'nonce'     => (string) $nonce,
696
					'code'      => (string) $api_user_code,
697
				)
698
			),
699
			$jetpack_token->secret
700
		);
701
702
		if ( ! hash_equals( $hmac, $verify ) ) {
703
			return false;
704
		}
705
706
		return $user_id;
707
	}
708
709
	/**
710
	 * Disconnect this blog from the connected wordpress.com account
711
	 *
712
	 * @return boolean
713
	 */
714
	public function disconnect_blog() {
715
716
		// For tracking.
717
		if ( ! empty( $this->user->ID ) ) {
718
			wp_set_current_user( $this->user->ID );
719
		}
720
721
		/**
722
		 * Fired when we want to log an event to the Jetpack event log.
723
		 *
724
		 * @since 7.7.0
725
		 *
726
		 * @param string $code Unique name for the event.
727
		 * @param string $data Optional data about the event.
728
		 */
729
		do_action( 'jetpack_event_log', 'disconnect' );
730
		Jetpack::disconnect();
731
732
		return true;
733
	}
734
735
	/**
736
	 * Unlink a user from WordPress.com
737
	 *
738
	 * This will fail if called by the Master User.
739
	 */
740
	public function unlink_user() {
741
		/**
742
		 * Fired when we want to log an event to the Jetpack event log.
743
		 *
744
		 * @since 7.7.0
745
		 *
746
		 * @param string $code Unique name for the event.
747
		 * @param string $data Optional data about the event.
748
		 */
749
		do_action( 'jetpack_event_log', 'unlink' );
750
		return Connection_Manager::disconnect_user();
751
	}
752
753
	/**
754
	 * Returns any object that is able to be synced.
755
	 *
756
	 * @deprecated since 7.8.0
757
	 * @see Automattic\Jetpack\Sync\Sender::sync_object()
758
	 *
759
	 * @param array $args the synchronized object parameters.
760
	 * @return string Encoded sync object.
761
	 */
762
	public function sync_object( $args ) {
763
		_deprecated_function( __METHOD__, 'jetpack-7.8', 'Automattic\\Jetpack\\Sync\\Sender::sync_object' );
764
		return Sender::get_instance()->sync_object( $args );
765
	}
766
767
	/**
768
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
769
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
770
	 * and the remote Jetpack site.
771
	 *
772
	 * @return array
773
	 */
774
	public function validate_urls_for_idc_mitigation() {
775
		return array(
776
			'home'    => Functions::home_url(),
777
			'siteurl' => Functions::site_url(),
778
		);
779
	}
780
781
	/**
782
	 * Returns what features are available. Uses the slug of the module files.
783
	 *
784
	 * @return array
785
	 */
786 View Code Duplication
	public function features_available() {
787
		$raw_modules = Jetpack::get_available_modules();
788
		$modules     = array();
789
		foreach ( $raw_modules as $module ) {
790
			$modules[] = Jetpack::get_module_slug( $module );
791
		}
792
793
		return $modules;
794
	}
795
796
	/**
797
	 * Returns what features are enabled. Uses the slug of the modules files.
798
	 *
799
	 * @return array
800
	 */
801 View Code Duplication
	public function features_enabled() {
802
		$raw_modules = Jetpack::get_active_modules();
803
		$modules     = array();
804
		foreach ( $raw_modules as $module ) {
805
			$modules[] = Jetpack::get_module_slug( $module );
806
		}
807
808
		return $modules;
809
	}
810
811
	/**
812
	 * Updates the attachment parent object.
813
	 *
814
	 * @param array $args attachment and parent identifiers.
815
	 */
816
	public function update_attachment_parent( $args ) {
817
		$attachment_id = (int) $args[0];
818
		$parent_id     = (int) $args[1];
819
820
		return wp_update_post(
821
			array(
822
				'ID'          => $attachment_id,
823
				'post_parent' => $parent_id,
824
			)
825
		);
826
	}
827
828
	/**
829
	 * Serve a JSON API request.
830
	 *
831
	 * @param array $args request arguments.
832
	 */
833
	public function json_api( $args = array() ) {
834
		$json_api_args        = $args[0];
835
		$verify_api_user_args = $args[1];
836
837
		$method       = (string) $json_api_args[0];
838
		$url          = (string) $json_api_args[1];
839
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
840
		$user_details = (array) $json_api_args[4];
841
		$locale       = (string) $json_api_args[5];
842
843
		if ( ! $verify_api_user_args ) {
844
			$user_id = 0;
845
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
846
			$user_id = (int) $verify_api_user_args[1];
847
			if ( $user_id ) {
848
				$user = get_user_by( 'id', $user_id );
849
				if ( ! $user || is_wp_error( $user ) ) {
850
					return false;
851
				}
852
			}
853
		} else {
854
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
855
			if ( ! $user_id ) {
856
				return false;
857
			}
858
		}
859
860
		/* phpcs:ignore
861
		 debugging
862
		error_log( "-- begin json api via jetpack debugging -- " );
863
		error_log( "METHOD: $method" );
864
		error_log( "URL: $url" );
865
		error_log( "POST BODY: $post_body" );
866
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
867
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
868
		error_log( "-- end json api via jetpack debugging -- " );
869
		*/
870
871
		if ( 'en' !== $locale ) {
872
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
873
			$new_locale = $locale;
874
			if ( strpos( $locale, '-' ) !== false ) {
875
				$locale_pieces = explode( '-', $locale );
876
				$new_locale    = $locale_pieces[0];
877
				$new_locale   .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
878
			} else {
879
				// .com might pass 'fr' because thats what our language files are named as, where core seems
880
				// to do fr_FR - so try that if we don't think we can load the file.
881
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
882
					$new_locale = $locale . '_' . strtoupper( $locale );
883
				}
884
			}
885
886
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
887
				unload_textdomain( 'default' );
888
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
889
			}
890
		}
891
892
		$old_user = wp_get_current_user();
893
		wp_set_current_user( $user_id );
894
895
		if ( $user_id ) {
896
			$token_key = false;
897
		} else {
898
			$verified  = $this->connection->verify_xml_rpc_signature();
899
			$token_key = $verified['token_key'];
900
		}
901
902
		$token = $this->connection->get_access_token( $user_id, $token_key );
903
		if ( ! $token || is_wp_error( $token ) ) {
904
			return false;
905
		}
906
907
		define( 'REST_API_REQUEST', true );
908
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
909
910
		// needed?
911
		require_once ABSPATH . 'wp-admin/includes/admin.php';
912
913
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
914
		$api                        = WPCOM_JSON_API::init( $method, $url, $post_body );
915
		$api->token_details['user'] = $user_details;
916
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
917
918
		$display_errors = ini_set( 'display_errors', 0 ); // phpcs:ignore WordPress.PHP.IniSet
919
		ob_start();
920
		$api->serve( false );
921
		$output = ob_get_clean();
922
		ini_set( 'display_errors', $display_errors ); // phpcs:ignore WordPress.PHP.IniSet
923
924
		$nonce = wp_generate_password( 10, false );
925
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
926
927
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
928
929
		return array(
930
			(string) $output,
931
			(string) $nonce,
932
			(string) $hmac,
933
		);
934
	}
935
936
	/**
937
	 * Handles authorization actions after connecting a site, such as enabling modules.
938
	 *
939
	 * This do_post_authorization() is used in this class, as opposed to calling
940
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
941
	 *
942
	 * @return void
943
	 */
944
	public function do_post_authorization() {
945
		/** This filter is documented in class.jetpack-cli.php */
946
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
947
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
948
	}
949
}
950