Completed
Push — update/xmlrpc_methods_visibili... ( 48b747...919878 )
by
unknown
130:27 queued 119:53
created

Jetpack_XMLRPC_Server::xmlrpc_methods()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

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