Completed
Push — add/connection-error-handling ( a8d1fc )
by
unknown
07:44
created

Manager::admin_notices()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * The Jetpack Connection manager class file.
4
 *
5
 * @package automattic/jetpack-connection
6
 */
7
8
namespace Automattic\Jetpack\Connection;
9
10
use Automattic\Jetpack\Constants;
11
use Automattic\Jetpack\Roles;
12
use Automattic\Jetpack\Tracking;
13
14
/**
15
 * The Jetpack Connection Manager class that is used as a single gateway between WordPress.com
16
 * and Jetpack.
17
 */
18
class Manager {
19
20
	const SECRETS_MISSING        = 'secrets_missing';
21
	const SECRETS_EXPIRED        = 'secrets_expired';
22
	const SECRETS_OPTION_NAME    = 'jetpack_secrets';
23
	const MAGIC_NORMAL_TOKEN_KEY = ';normal;';
24
	const JETPACK_MASTER_USER    = true;
25
26
	/**
27
	 * The procedure that should be run to generate secrets.
28
	 *
29
	 * @var Callable
30
	 */
31
	protected $secret_callable;
32
33
	/**
34
	 * A copy of the raw POST data for signature verification purposes.
35
	 *
36
	 * @var String
37
	 */
38
	protected $raw_post_data;
39
40
	/**
41
	 * Verification data needs to be stored to properly verify everything.
42
	 *
43
	 * @var Object
44
	 */
45
	private $xmlrpc_verification = null;
46
47
	/**
48
	 * Plugin management object.
49
	 *
50
	 * @var Plugin
51
	 */
52
	private $plugin = null;
53
54
	/**
55
	 * Initialize the object.
56
	 * Make sure to call the "Configure" first.
57
	 *
58
	 * @param string $plugin_slug Slug of the plugin using the connection (optional, but encouraged).
0 ignored issues
show
Documentation introduced by
Should the type for parameter $plugin_slug 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...
59
	 *
60
	 * @see \Automattic\Jetpack\Config
61
	 */
62
	public function __construct( $plugin_slug = null ) {
63
		if ( $plugin_slug && is_string( $plugin_slug ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $plugin_slug of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
64
			$this->set_plugin_instance( new Plugin( $plugin_slug ) );
65
		}
66
	}
67
68
	/**
69
	 * Initializes required listeners. This is done separately from the constructors
70
	 * because some objects sometimes need to instantiate separate objects of this class.
71
	 *
72
	 * @todo Implement a proper nonce verification.
73
	 */
74
	public static function configure() {
75
		$manager = new self();
76
77
		$manager->setup_xmlrpc_handlers(
78
			$_GET, // phpcs:ignore WordPress.Security.NonceVerification.Recommended
79
			$manager->is_active(),
80
			$manager->verify_xml_rpc_signature()
0 ignored issues
show
Bug introduced by
It seems like $manager->verify_xml_rpc_signature() targeting Automattic\Jetpack\Conne...ify_xml_rpc_signature() can also be of type array; however, Automattic\Jetpack\Conne...setup_xmlrpc_handlers() does only seem to accept boolean, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

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

An additional type check may prevent trouble.

Loading history...
81
		);
82
83
		if ( $manager->is_active() ) {
84
			add_filter( 'xmlrpc_methods', array( $manager, 'public_xmlrpc_methods' ) );
85
		} else {
86
			add_action( 'rest_api_init', array( $manager, 'initialize_rest_api_registration_connector' ) );
87
		}
88
89
		add_action( 'jetpack_clean_nonces', array( $manager, 'clean_nonces' ) );
90
		if ( ! wp_next_scheduled( 'jetpack_clean_nonces' ) ) {
91
			wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
92
		}
93
94
		add_filter(
95
			'jetpack_constant_default_value',
96
			__NAMESPACE__ . '\Utils::jetpack_api_constant_filter',
97
			10,
98
			2
99
		);
100
101
		add_action( 'plugins_loaded', __NAMESPACE__ . '\Plugin_Storage::configure', 100 );
102
103
		add_action( 'admin_notices', array( $manager, 'admin_notices' ) );
104
105
	}
106
107
	/**
108
	 * Add notices to the Admin Notices section if there are known connection errors
109
	 *
110
	 * @return void
111
	 */
112
	public function admin_notices() {
113
		$errors_manager = new Errors();
114
		$errors         = $errors_manager->get_stored_errors();
115
		if ( count( $errors ) ) {
116
			// build the error message.
117
		}
118
	}
119
120
	/**
121
	 * Sets up the XMLRPC request handlers.
122
	 *
123
	 * @param array                  $request_params incoming request parameters.
124
	 * @param Boolean                $is_active whether the connection is currently active.
125
	 * @param Boolean                $is_signed whether the signature check has been successful.
126
	 * @param \Jetpack_XMLRPC_Server $xmlrpc_server (optional) an instance of the server to use instead of instantiating a new one.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $xmlrpc_server not be null|\Jetpack_XMLRPC_Server?

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...
127
	 */
128
	public function setup_xmlrpc_handlers(
129
		$request_params,
130
		$is_active,
131
		$is_signed,
132
		\Jetpack_XMLRPC_Server $xmlrpc_server = null
133
	) {
134
		add_filter( 'xmlrpc_blog_options', array( $this, 'xmlrpc_options' ), 1000, 2 );
135
136
		if (
137
			! isset( $request_params['for'] )
138
			|| 'jetpack' !== $request_params['for']
139
		) {
140
			return false;
141
		}
142
143
		// Alternate XML-RPC, via ?for=jetpack&jetpack=comms.
144
		if (
145
			isset( $request_params['jetpack'] )
146
			&& 'comms' === $request_params['jetpack']
147
		) {
148
			if ( ! Constants::is_defined( 'XMLRPC_REQUEST' ) ) {
149
				// Use the real constant here for WordPress' sake.
150
				define( 'XMLRPC_REQUEST', true );
151
			}
152
153
			add_action( 'template_redirect', array( $this, 'alternate_xmlrpc' ) );
154
155
			add_filter( 'xmlrpc_methods', array( $this, 'remove_non_jetpack_xmlrpc_methods' ), 1000 );
156
		}
157
158
		if ( ! Constants::get_constant( 'XMLRPC_REQUEST' ) ) {
159
			return false;
160
		}
161
		// Display errors can cause the XML to be not well formed.
162
		@ini_set( 'display_errors', false ); // phpcs:ignore
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
163
164
		if ( $xmlrpc_server ) {
165
			$this->xmlrpc_server = $xmlrpc_server;
0 ignored issues
show
Bug introduced by
The property xmlrpc_server does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
166
		} else {
167
			$this->xmlrpc_server = new \Jetpack_XMLRPC_Server();
168
		}
169
170
		$this->require_jetpack_authentication();
171
172
		if ( $is_active ) {
173
			// Hack to preserve $HTTP_RAW_POST_DATA.
174
			add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
175
176
			if ( $is_signed ) {
177
				// The actual API methods.
178
				add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'xmlrpc_methods' ) );
179
			} else {
180
				// The jetpack.authorize method should be available for unauthenticated users on a site with an
181
				// active Jetpack connection, so that additional users can link their account.
182
				add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'authorize_xmlrpc_methods' ) );
183
			}
184
		} else {
185
			// The bootstrap API methods.
186
			add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'bootstrap_xmlrpc_methods' ) );
187
188
			if ( $is_signed ) {
189
				// The jetpack Provision method is available for blog-token-signed requests.
190
				add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'provision_xmlrpc_methods' ) );
191
			} else {
192
				new XMLRPC_Connector( $this );
193
			}
194
		}
195
196
		// Now that no one can authenticate, and we're whitelisting all XML-RPC methods, force enable_xmlrpc on.
197
		add_filter( 'pre_option_enable_xmlrpc', '__return_true' );
198
		return true;
199
	}
200
201
	/**
202
	 * Initializes the REST API connector on the init hook.
203
	 */
204
	public function initialize_rest_api_registration_connector() {
205
		new REST_Connector( $this );
206
	}
207
208
	/**
209
	 * Since a lot of hosts use a hammer approach to "protecting" WordPress sites,
210
	 * and just blanket block all requests to /xmlrpc.php, or apply other overly-sensitive
211
	 * security/firewall policies, we provide our own alternate XML RPC API endpoint
212
	 * which is accessible via a different URI. Most of the below is copied directly
213
	 * from /xmlrpc.php so that we're replicating it as closely as possible.
214
	 *
215
	 * @todo Tighten $wp_xmlrpc_server_class a bit to make sure it doesn't do bad things.
216
	 */
217
	public function alternate_xmlrpc() {
218
		// phpcs:disable PHPCompatibility.Variables.RemovedPredefinedGlobalVariables.http_raw_post_dataDeprecatedRemoved
219
		// phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
220
		global $HTTP_RAW_POST_DATA;
221
222
		// Some browser-embedded clients send cookies. We don't want them.
223
		$_COOKIE = array();
224
225
		// A fix for mozBlog and other cases where '<?xml' isn't on the very first line.
226
		if ( isset( $HTTP_RAW_POST_DATA ) ) {
227
			$HTTP_RAW_POST_DATA = trim( $HTTP_RAW_POST_DATA );
228
		}
229
230
		// phpcs:enable
231
232
		include_once ABSPATH . 'wp-admin/includes/admin.php';
233
		include_once ABSPATH . WPINC . '/class-IXR.php';
234
		include_once ABSPATH . WPINC . '/class-wp-xmlrpc-server.php';
235
236
		/**
237
		 * Filters the class used for handling XML-RPC requests.
238
		 *
239
		 * @since 3.1.0
240
		 *
241
		 * @param string $class The name of the XML-RPC server class.
242
		 */
243
		$wp_xmlrpc_server_class = apply_filters( 'wp_xmlrpc_server_class', 'wp_xmlrpc_server' );
244
		$wp_xmlrpc_server       = new $wp_xmlrpc_server_class();
245
246
		// Fire off the request.
247
		nocache_headers();
248
		$wp_xmlrpc_server->serve_request();
249
250
		exit;
251
	}
252
253
	/**
254
	 * Removes all XML-RPC methods that are not `jetpack.*`.
255
	 * Only used in our alternate XML-RPC endpoint, where we want to
256
	 * ensure that Core and other plugins' methods are not exposed.
257
	 *
258
	 * @param array $methods a list of registered WordPress XMLRPC methods.
259
	 * @return array filtered $methods
260
	 */
261
	public function remove_non_jetpack_xmlrpc_methods( $methods ) {
262
		$jetpack_methods = array();
263
264
		foreach ( $methods as $method => $callback ) {
265
			if ( 0 === strpos( $method, 'jetpack.' ) ) {
266
				$jetpack_methods[ $method ] = $callback;
267
			}
268
		}
269
270
		return $jetpack_methods;
271
	}
272
273
	/**
274
	 * Removes all other authentication methods not to allow other
275
	 * methods to validate unauthenticated requests.
276
	 */
277
	public function require_jetpack_authentication() {
278
		// Don't let anyone authenticate.
279
		$_COOKIE = array();
280
		remove_all_filters( 'authenticate' );
281
		remove_all_actions( 'wp_login_failed' );
282
283
		if ( $this->is_active() ) {
284
			// Allow Jetpack authentication.
285
			add_filter( 'authenticate', array( $this, 'authenticate_jetpack' ), 10, 3 );
286
		}
287
	}
288
289
	/**
290
	 * Authenticates XML-RPC and other requests from the Jetpack Server
291
	 *
292
	 * @param WP_User|Mixed $user user object if authenticated.
293
	 * @param String        $username username.
294
	 * @param String        $password password string.
295
	 * @return WP_User|Mixed authenticated user or error.
296
	 */
297
	public function authenticate_jetpack( $user, $username, $password ) {
298
		if ( is_a( $user, '\\WP_User' ) ) {
299
			return $user;
300
		}
301
302
		$token_details = $this->verify_xml_rpc_signature();
303
304
		if ( ! $token_details ) {
305
			return $user;
306
		}
307
308
		if ( 'user' !== $token_details['type'] ) {
309
			return $user;
310
		}
311
312
		if ( ! $token_details['user_id'] ) {
313
			return $user;
314
		}
315
316
		nocache_headers();
317
318
		return new \WP_User( $token_details['user_id'] );
319
	}
320
321
	/**
322
	 * Verifies the signature of the current request.
323
	 *
324
	 * @return false|array
325
	 */
326
	public function verify_xml_rpc_signature() {
327
		if ( is_null( $this->xmlrpc_verification ) ) {
328
			$this->xmlrpc_verification = $this->internal_verify_xml_rpc_signature();
329
330
			if ( is_wp_error( $this->xmlrpc_verification ) ) {
331
				/**
332
				 * Action for logging XMLRPC signature verification errors. This data is sensitive.
333
				 *
334
				 * Error codes:
335
				 * - malformed_token
336
				 * - malformed_user_id
337
				 * - unknown_token
338
				 * - could_not_sign
339
				 * - invalid_nonce
340
				 * - signature_mismatch
341
				 *
342
				 * @since 7.5.0
343
				 *
344
				 * @param WP_Error $signature_verification_error The verification error
345
				 */
346
				do_action( 'jetpack_verify_signature_error', $this->xmlrpc_verification );
347
			}
348
		}
349
350
		return is_wp_error( $this->xmlrpc_verification ) ? false : $this->xmlrpc_verification;
351
	}
352
353
	/**
354
	 * Verifies the signature of the current request.
355
	 *
356
	 * This function has side effects and should not be used. Instead,
357
	 * use the memoized version `->verify_xml_rpc_signature()`.
358
	 *
359
	 * @internal
360
	 * @todo Refactor to use proper nonce verification.
361
	 */
362
	private function internal_verify_xml_rpc_signature() {
363
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
364
		// It's not for us.
365
		if ( ! isset( $_GET['token'] ) || empty( $_GET['signature'] ) ) {
366
			return false;
367
		}
368
369
		$signature_details = array(
370
			'token'     => isset( $_GET['token'] ) ? wp_unslash( $_GET['token'] ) : '',
371
			'timestamp' => isset( $_GET['timestamp'] ) ? wp_unslash( $_GET['timestamp'] ) : '',
372
			'nonce'     => isset( $_GET['nonce'] ) ? wp_unslash( $_GET['nonce'] ) : '',
373
			'body_hash' => isset( $_GET['body-hash'] ) ? wp_unslash( $_GET['body-hash'] ) : '',
374
			'method'    => wp_unslash( $_SERVER['REQUEST_METHOD'] ),
375
			'url'       => wp_unslash( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ), // Temp - will get real signature URL later.
376
			'signature' => isset( $_GET['signature'] ) ? wp_unslash( $_GET['signature'] ) : '',
377
		);
378
379
		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
380
		@list( $token_key, $version, $user_id ) = explode( ':', wp_unslash( $_GET['token'] ) );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
381
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
382
383
		$jetpack_api_version = Constants::get_constant( 'JETPACK__API_VERSION' );
384
385
		if (
386
			empty( $token_key )
387
		||
388
			empty( $version ) || strval( $jetpack_api_version ) !== $version ) {
389
			return new Connection_Error( 'malformed_token', 'Malformed token in request', compact( 'signature_details' ) );
0 ignored issues
show
Unused Code introduced by
The call to Connection_Error::__construct() has too many arguments starting with compact('signature_details').

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...
390
		}
391
392
		if ( '0' === $user_id ) {
393
			$token_type = 'blog';
394
			$user_id    = 0;
395
		} else {
396
			$token_type = 'user';
397
			if ( empty( $user_id ) || ! ctype_digit( $user_id ) ) {
398
				return new Connection_Error(
399
					'malformed_user_id',
400
					'Malformed user_id in request',
401
					compact( 'signature_details' )
0 ignored issues
show
Unused Code introduced by
The call to Connection_Error::__construct() has too many arguments starting with compact('signature_details').

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...
402
				);
403
			}
404
			$user_id = (int) $user_id;
405
406
			$user = new \WP_User( $user_id );
407
			if ( ! $user || ! $user->exists() ) {
408
				return new Connection_Error(
409
					'unknown_user',
410
					sprintf( 'User %d does not exist', $user_id ),
411
					compact( 'signature_details' )
0 ignored issues
show
Unused Code introduced by
The call to Connection_Error::__construct() has too many arguments starting with compact('signature_details').

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...
412
				);
413
			}
414
		}
415
416
		$token = $this->get_access_token( $user_id, $token_key, false );
417
		if ( is_wp_error( $token ) ) {
418
			$token->add_data( compact( 'signature_details' ) );
419
			return $token;
420
		} elseif ( ! $token ) {
421
			return new Connection_Error(
422
				'unknown_token',
423
				sprintf( 'Token %s:%s:%d does not exist', $token_key, $version, $user_id ),
424
				compact( 'signature_details' )
0 ignored issues
show
Unused Code introduced by
The call to Connection_Error::__construct() has too many arguments starting with compact('signature_details').

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...
425
			);
426
		}
427
428
		$jetpack_signature = new \Jetpack_Signature( $token->secret, (int) \Jetpack_Options::get_option( 'time_diff' ) );
429
		// phpcs:disable WordPress.Security.NonceVerification.Missing
430
		if ( isset( $_POST['_jetpack_is_multipart'] ) ) {
431
			$post_data   = $_POST;
432
			$file_hashes = array();
433
			foreach ( $post_data as $post_data_key => $post_data_value ) {
434
				if ( 0 !== strpos( $post_data_key, '_jetpack_file_hmac_' ) ) {
435
					continue;
436
				}
437
				$post_data_key                 = substr( $post_data_key, strlen( '_jetpack_file_hmac_' ) );
438
				$file_hashes[ $post_data_key ] = $post_data_value;
439
			}
440
441
			foreach ( $file_hashes as $post_data_key => $post_data_value ) {
442
				unset( $post_data[ "_jetpack_file_hmac_{$post_data_key}" ] );
443
				$post_data[ $post_data_key ] = $post_data_value;
444
			}
445
446
			ksort( $post_data );
447
448
			$body = http_build_query( stripslashes_deep( $post_data ) );
449
		} elseif ( is_null( $this->raw_post_data ) ) {
450
			$body = file_get_contents( 'php://input' );
451
		} else {
452
			$body = null;
453
		}
454
		// phpcs:enable
455
456
		$signature = $jetpack_signature->sign_current_request(
457
			array( 'body' => is_null( $body ) ? $this->raw_post_data : $body )
458
		);
459
460
		$signature_details['url'] = $jetpack_signature->current_request_url;
461
462
		if ( ! $signature ) {
463
			return new Connection_Error(
464
				'could_not_sign',
465
				'Unknown signature error',
466
				compact( 'signature_details' )
0 ignored issues
show
Unused Code introduced by
The call to Connection_Error::__construct() has too many arguments starting with compact('signature_details').

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...
467
			);
468
		} elseif ( is_wp_error( $signature ) ) {
469
			return $signature;
470
		}
471
472
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
473
		$timestamp = (int) $_GET['timestamp'];
474
		$nonce     = stripslashes( (string) $_GET['nonce'] );
475
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
476
477
		// Use up the nonce regardless of whether the signature matches.
478
		if ( ! $this->add_nonce( $timestamp, $nonce ) ) {
479
			return new Connection_Error(
480
				'invalid_nonce',
481
				'Could not add nonce',
482
				compact( 'signature_details' )
0 ignored issues
show
Unused Code introduced by
The call to Connection_Error::__construct() has too many arguments starting with compact('signature_details').

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...
483
			);
484
		}
485
486
		// Be careful about what you do with this debugging data.
487
		// If a malicious requester has access to the expected signature,
488
		// bad things might be possible.
489
		$signature_details['expected'] = $signature;
490
491
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
492
		if ( ! hash_equals( $signature, $_GET['signature'] ) ) {
493
			return new Connection_Error(
494
				'signature_mismatch',
495
				'Signature mismatch',
496
				compact( 'signature_details' )
0 ignored issues
show
Unused Code introduced by
The call to Connection_Error::__construct() has too many arguments starting with compact('signature_details').

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...
497
			);
498
		}
499
500
		/**
501
		 * Action for additional token checking.
502
		 *
503
		 * @since 7.7.0
504
		 *
505
		 * @param array $post_data request data.
506
		 * @param array $token_data token data.
507
		 */
508
		return apply_filters(
509
			'jetpack_signature_check_token',
510
			array(
511
				'type'      => $token_type,
512
				'token_key' => $token_key,
513
				'user_id'   => $token->external_user_id,
514
			),
515
			$token,
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $token.

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...
516
			$this->raw_post_data
517
		);
518
	}
519
520
	/**
521
	 * Returns true if the current site is connected to WordPress.com.
522
	 *
523
	 * @return Boolean is the site connected?
524
	 */
525
	public function is_active() {
526
		return (bool) $this->get_access_token( self::JETPACK_MASTER_USER );
0 ignored issues
show
Documentation introduced by
self::JETPACK_MASTER_USER is of type boolean, but the function expects a false|integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
527
	}
528
529
	/**
530
	 * Returns true if the site has both a token and a blog id, which indicates a site has been registered.
531
	 *
532
	 * @access public
533
	 *
534
	 * @return bool
535
	 */
536
	public function is_registered() {
537
		$blog_id   = \Jetpack_Options::get_option( 'id' );
538
		$has_token = $this->is_active();
539
		return $blog_id && $has_token;
540
	}
541
542
	/**
543
	 * Checks to see if the connection owner of the site is missing.
544
	 *
545
	 * @return bool
546
	 */
547
	public function is_missing_connection_owner() {
548
		$connection_owner = $this->get_connection_owner_id();
549
		if ( ! get_user_by( 'id', $connection_owner ) ) {
550
			return true;
551
		}
552
553
		return false;
554
	}
555
556
	/**
557
	 * Returns true if the user with the specified identifier is connected to
558
	 * WordPress.com.
559
	 *
560
	 * @param Integer|Boolean $user_id the user identifier.
561
	 * @return Boolean is the user connected?
562
	 */
563
	public function is_user_connected( $user_id = false ) {
564
		$user_id = false === $user_id ? get_current_user_id() : absint( $user_id );
565
		if ( ! $user_id ) {
566
			return false;
567
		}
568
569
		return (bool) $this->get_access_token( $user_id );
570
	}
571
572
	/**
573
	 * Returns the local user ID of the connection owner.
574
	 *
575
	 * @return string|int Returns the ID of the connection owner or False if no connection owner found.
576
	 */
577 View Code Duplication
	public function get_connection_owner_id() {
578
		$user_token       = $this->get_access_token( self::JETPACK_MASTER_USER );
0 ignored issues
show
Documentation introduced by
self::JETPACK_MASTER_USER is of type boolean, but the function expects a false|integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
579
		$connection_owner = false;
580
		if ( $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) ) {
581
			$connection_owner = $user_token->external_user_id;
582
		}
583
584
		return $connection_owner;
585
	}
586
587
	/**
588
	 * Returns an array of user_id's that have user tokens for communicating with wpcom.
589
	 * Able to select by specific capability.
590
	 *
591
	 * @param string $capability The capability of the user.
592
	 * @return array Array of WP_User objects if found.
593
	 */
594
	public function get_connected_users( $capability = 'any' ) {
595
		$connected_users    = array();
596
		$connected_user_ids = array_keys( \Jetpack_Options::get_option( 'user_tokens' ) );
597
598
		if ( ! empty( $connected_user_ids ) ) {
599
			foreach ( $connected_user_ids as $id ) {
600
				// Check for capability.
601
				if ( 'any' !== $capability && ! user_can( $id, $capability ) ) {
602
					continue;
603
				}
604
605
				$connected_users[] = get_userdata( $id );
606
			}
607
		}
608
609
		return $connected_users;
610
	}
611
612
	/**
613
	 * Get the wpcom user data of the current|specified connected user.
614
	 *
615
	 * @todo Refactor to properly load the XMLRPC client independently.
616
	 *
617
	 * @param Integer $user_id the user identifier.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user_id not be integer|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...
618
	 * @return Object the user object.
619
	 */
620 View Code Duplication
	public function get_connected_user_data( $user_id = null ) {
621
		if ( ! $user_id ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user_id of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
622
			$user_id = get_current_user_id();
623
		}
624
625
		$transient_key    = "jetpack_connected_user_data_$user_id";
626
		$cached_user_data = get_transient( $transient_key );
627
628
		if ( $cached_user_data ) {
629
			return $cached_user_data;
630
		}
631
632
		$xml = new \Jetpack_IXR_Client(
633
			array(
634
				'user_id' => $user_id,
635
			)
636
		);
637
		$xml->query( 'wpcom.getUser' );
638
		if ( ! $xml->isError() ) {
639
			$user_data = $xml->getResponse();
640
			set_transient( $transient_key, $xml->getResponse(), DAY_IN_SECONDS );
641
			return $user_data;
642
		}
643
644
		return false;
645
	}
646
647
	/**
648
	 * Returns a user object of the connection owner.
649
	 *
650
	 * @return object|false False if no connection owner found.
651
	 */
652 View Code Duplication
	public function get_connection_owner() {
653
		$user_token = $this->get_access_token( self::JETPACK_MASTER_USER );
0 ignored issues
show
Documentation introduced by
self::JETPACK_MASTER_USER is of type boolean, but the function expects a false|integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
654
655
		$connection_owner = false;
656
		if ( $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) ) {
657
			$connection_owner = get_userdata( $user_token->external_user_id );
658
		}
659
660
		return $connection_owner;
661
	}
662
663
	/**
664
	 * Returns true if the provided user is the Jetpack connection owner.
665
	 * If user ID is not specified, the current user will be used.
666
	 *
667
	 * @param Integer|Boolean $user_id the user identifier. False for current user.
668
	 * @return Boolean True the user the connection owner, false otherwise.
669
	 */
670 View Code Duplication
	public function is_connection_owner( $user_id = false ) {
671
		if ( ! $user_id ) {
672
			$user_id = get_current_user_id();
673
		}
674
675
		$user_token = $this->get_access_token( self::JETPACK_MASTER_USER );
0 ignored issues
show
Documentation introduced by
self::JETPACK_MASTER_USER is of type boolean, but the function expects a false|integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
676
677
		return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && $user_id === $user_token->external_user_id;
678
	}
679
680
	/**
681
	 * Connects the user with a specified ID to a WordPress.com user using the
682
	 * remote login flow.
683
	 *
684
	 * @access public
685
	 *
686
	 * @param Integer $user_id (optional) the user identifier, defaults to current user.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user_id not be integer|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...
687
	 * @param String  $redirect_url the URL to redirect the user to for processing, defaults to
0 ignored issues
show
Documentation introduced by
Should the type for parameter $redirect_url 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...
688
	 *                              admin_url().
689
	 * @return WP_Error only in case of a failed user lookup.
690
	 */
691
	public function connect_user( $user_id = null, $redirect_url = null ) {
692
		$user = null;
0 ignored issues
show
Unused Code introduced by
$user is not used, you could remove the assignment.

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

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

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

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

Loading history...
693
		if ( null === $user_id ) {
694
			$user = wp_get_current_user();
695
		} else {
696
			$user = get_user_by( 'ID', $user_id );
697
		}
698
699
		if ( empty( $user ) ) {
700
			return new \WP_Error( 'user_not_found', 'Attempting to connect a non-existent user.' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'user_not_found'.

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...
701
		}
702
703
		if ( null === $redirect_url ) {
704
			$redirect_url = admin_url();
0 ignored issues
show
Unused Code introduced by
$redirect_url is not used, you could remove the assignment.

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

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

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

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

Loading history...
705
		}
706
707
		// Using wp_redirect intentionally because we're redirecting outside.
708
		wp_redirect( $this->get_authorization_url( $user ) ); // phpcs:ignore WordPress.Security.SafeRedirect
709
		exit();
710
	}
711
712
	/**
713
	 * Unlinks the current user from the linked WordPress.com user.
714
	 *
715
	 * @access public
716
	 * @static
717
	 *
718
	 * @todo Refactor to properly load the XMLRPC client independently.
719
	 *
720
	 * @param Integer $user_id the user identifier.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user_id not be integer|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...
721
	 * @return Boolean Whether the disconnection of the user was successful.
722
	 */
723
	public static function disconnect_user( $user_id = null ) {
724
		$tokens = \Jetpack_Options::get_option( 'user_tokens' );
725
		if ( ! $tokens ) {
726
			return false;
727
		}
728
729
		$user_id = empty( $user_id ) ? get_current_user_id() : intval( $user_id );
730
731
		if ( \Jetpack_Options::get_option( 'master_user' ) === $user_id ) {
732
			return false;
733
		}
734
735
		if ( ! isset( $tokens[ $user_id ] ) ) {
736
			return false;
737
		}
738
739
		$xml = new \Jetpack_IXR_Client( compact( 'user_id' ) );
740
		$xml->query( 'jetpack.unlink_user', $user_id );
741
742
		unset( $tokens[ $user_id ] );
743
744
		\Jetpack_Options::update_option( 'user_tokens', $tokens );
745
746
		/**
747
		 * Fires after the current user has been unlinked from WordPress.com.
748
		 *
749
		 * @since 4.1.0
750
		 *
751
		 * @param int $user_id The current user's ID.
752
		 */
753
		do_action( 'jetpack_unlinked_user', $user_id );
754
755
		return true;
756
	}
757
758
	/**
759
	 * Returns the requested Jetpack API URL.
760
	 *
761
	 * @param String $relative_url the relative API path.
762
	 * @return String API URL.
763
	 */
764
	public function api_url( $relative_url ) {
765
		$api_base    = Constants::get_constant( 'JETPACK__API_BASE' );
766
		$api_version = '/' . Constants::get_constant( 'JETPACK__API_VERSION' ) . '/';
767
768
		/**
769
		 * Filters whether the connection manager should use the iframe authorization
770
		 * flow instead of the regular redirect-based flow.
771
		 *
772
		 * @since 8.3.0
773
		 *
774
		 * @param Boolean $is_iframe_flow_used should the iframe flow be used, defaults to false.
775
		 */
776
		$iframe_flow = apply_filters( 'jetpack_use_iframe_authorization_flow', false );
777
778
		// Do not modify anything that is not related to authorize requests.
779
		if ( 'authorize' === $relative_url && $iframe_flow ) {
780
			$relative_url = 'authorize_iframe';
781
		}
782
783
		/**
784
		 * Filters the API URL that Jetpack uses for server communication.
785
		 *
786
		 * @since 8.0.0
787
		 *
788
		 * @param String $url the generated URL.
789
		 * @param String $relative_url the relative URL that was passed as an argument.
790
		 * @param String $api_base the API base string that is being used.
791
		 * @param String $api_version the API version string that is being used.
792
		 */
793
		return apply_filters(
794
			'jetpack_api_url',
795
			rtrim( $api_base . $relative_url, '/\\' ) . $api_version,
796
			$relative_url,
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $relative_url.

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...
797
			$api_base,
798
			$api_version
799
		);
800
	}
801
802
	/**
803
	 * Returns the Jetpack XMLRPC WordPress.com API endpoint URL.
804
	 *
805
	 * @return String XMLRPC API URL.
806
	 */
807
	public function xmlrpc_api_url() {
808
		$base = preg_replace(
809
			'#(https?://[^?/]+)(/?.*)?$#',
810
			'\\1',
811
			Constants::get_constant( 'JETPACK__API_BASE' )
812
		);
813
		return untrailingslashit( $base ) . '/xmlrpc.php';
814
	}
815
816
	/**
817
	 * Attempts Jetpack registration which sets up the site for connection. Should
818
	 * remain public because the call to action comes from the current site, not from
819
	 * WordPress.com.
820
	 *
821
	 * @param String $api_endpoint (optional) an API endpoint to use, defaults to 'register'.
822
	 * @return Integer zero on success, or a bitmask on failure.
823
	 */
824
	public function register( $api_endpoint = 'register' ) {
825
		add_action( 'pre_update_jetpack_option_register', array( '\\Jetpack_Options', 'delete_option' ) );
826
		$secrets = $this->generate_secrets( 'register', get_current_user_id(), 600 );
827
828
		if (
829
			empty( $secrets['secret_1'] ) ||
830
			empty( $secrets['secret_2'] ) ||
831
			empty( $secrets['exp'] )
832
		) {
833
			return new \WP_Error( 'missing_secrets' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_secrets'.

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...
834
		}
835
836
		// Better to try (and fail) to set a higher timeout than this system
837
		// supports than to have register fail for more users than it should.
838
		$timeout = $this->set_min_time_limit( 60 ) / 2;
839
840
		$gmt_offset = get_option( 'gmt_offset' );
841
		if ( ! $gmt_offset ) {
842
			$gmt_offset = 0;
843
		}
844
845
		$stats_options = get_option( 'stats_options' );
846
		$stats_id      = isset( $stats_options['blog_id'] )
847
			? $stats_options['blog_id']
848
			: null;
849
850
		/**
851
		 * Filters the request body for additional property addition.
852
		 *
853
		 * @since 7.7.0
854
		 *
855
		 * @param array $post_data request data.
856
		 * @param Array $token_data token data.
857
		 */
858
		$body = apply_filters(
859
			'jetpack_register_request_body',
860
			array(
861
				'siteurl'         => site_url(),
862
				'home'            => home_url(),
863
				'gmt_offset'      => $gmt_offset,
864
				'timezone_string' => (string) get_option( 'timezone_string' ),
865
				'site_name'       => (string) get_option( 'blogname' ),
866
				'secret_1'        => $secrets['secret_1'],
867
				'secret_2'        => $secrets['secret_2'],
868
				'site_lang'       => get_locale(),
869
				'timeout'         => $timeout,
870
				'stats_id'        => $stats_id,
871
				'state'           => get_current_user_id(),
872
				'site_created'    => $this->get_assumed_site_creation_date(),
873
				'jetpack_version' => Constants::get_constant( 'JETPACK__VERSION' ),
874
				'ABSPATH'         => Constants::get_constant( 'ABSPATH' ),
875
			)
876
		);
877
878
		$args = array(
879
			'method'  => 'POST',
880
			'body'    => $body,
881
			'headers' => array(
882
				'Accept' => 'application/json',
883
			),
884
			'timeout' => $timeout,
885
		);
886
887
		$args['body'] = $this->apply_activation_source_to_args( $args['body'] );
888
889
		// TODO: fix URLs for bad hosts.
890
		$response = Client::_wp_remote_request(
891
			$this->api_url( $api_endpoint ),
892
			$args,
893
			true
894
		);
895
896
		// Make sure the response is valid and does not contain any Jetpack errors.
897
		$registration_details = $this->validate_remote_register_response( $response );
898
899
		if ( is_wp_error( $registration_details ) ) {
900
			return $registration_details;
901
		} elseif ( ! $registration_details ) {
902
			return new \WP_Error(
903
				'unknown_error',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unknown_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...
904
				'Unknown error registering your Jetpack site.',
905
				wp_remote_retrieve_response_code( $response )
906
			);
907
		}
908
909
		if ( empty( $registration_details->jetpack_secret ) || ! is_string( $registration_details->jetpack_secret ) ) {
910
			return new \WP_Error(
911
				'jetpack_secret',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'jetpack_secret'.

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...
912
				'Unable to validate registration of your Jetpack site.',
913
				wp_remote_retrieve_response_code( $response )
914
			);
915
		}
916
917
		if ( isset( $registration_details->jetpack_public ) ) {
918
			$jetpack_public = (int) $registration_details->jetpack_public;
919
		} else {
920
			$jetpack_public = false;
921
		}
922
923
		\Jetpack_Options::update_options(
924
			array(
925
				'id'         => (int) $registration_details->jetpack_id,
926
				'blog_token' => (string) $registration_details->jetpack_secret,
927
				'public'     => $jetpack_public,
928
			)
929
		);
930
931
		/**
932
		 * Fires when a site is registered on WordPress.com.
933
		 *
934
		 * @since 3.7.0
935
		 *
936
		 * @param int $json->jetpack_id Jetpack Blog ID.
937
		 * @param string $json->jetpack_secret Jetpack Blog Token.
938
		 * @param int|bool $jetpack_public Is the site public.
939
		 */
940
		do_action(
941
			'jetpack_site_registered',
942
			$registration_details->jetpack_id,
943
			$registration_details->jetpack_secret,
944
			$jetpack_public
945
		);
946
947
		if ( isset( $registration_details->token ) ) {
948
			/**
949
			 * Fires when a user token is sent along with the registration data.
950
			 *
951
			 * @since 7.6.0
952
			 *
953
			 * @param object $token the administrator token for the newly registered site.
954
			 */
955
			do_action( 'jetpack_site_registered_user_token', $registration_details->token );
956
		}
957
958
		return true;
959
	}
960
961
	/**
962
	 * Takes the response from the Jetpack register new site endpoint and
963
	 * verifies it worked properly.
964
	 *
965
	 * @since 2.6
966
	 *
967
	 * @param Mixed $response the response object, or the error object.
968
	 * @return string|WP_Error A JSON object on success or WP_Error on failures
969
	 **/
970
	protected function validate_remote_register_response( $response ) {
971
		if ( is_wp_error( $response ) ) {
972
			return new \WP_Error(
973
				'register_http_request_failed',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'register_http_request_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...
974
				$response->get_error_message()
975
			);
976
		}
977
978
		$code   = wp_remote_retrieve_response_code( $response );
979
		$entity = wp_remote_retrieve_body( $response );
980
981
		if ( $entity ) {
982
			$registration_response = json_decode( $entity );
983
		} else {
984
			$registration_response = false;
985
		}
986
987
		$code_type = intval( $code / 100 );
988
		if ( 5 === $code_type ) {
989
			return new \WP_Error( 'wpcom_5??', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'wpcom_5??'.

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...
990
		} elseif ( 408 === $code ) {
991
			return new \WP_Error( 'wpcom_408', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'wpcom_408'.

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...
992
		} elseif ( ! empty( $registration_response->error ) ) {
993
			if (
994
				'xml_rpc-32700' === $registration_response->error
995
				&& ! function_exists( 'xml_parser_create' )
996
			) {
997
				$error_description = __( "PHP's XML extension is not available. Jetpack requires the XML extension to communicate with WordPress.com. Please contact your hosting provider to enable PHP's XML extension.", 'jetpack' );
998
			} else {
999
				$error_description = isset( $registration_response->error_description )
1000
					? (string) $registration_response->error_description
1001
					: '';
1002
			}
1003
1004
			return new \WP_Error(
1005
				(string) $registration_response->error,
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with (string) $registration_response->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...
1006
				$error_description,
1007
				$code
1008
			);
1009
		} elseif ( 200 !== $code ) {
1010
			return new \WP_Error( 'wpcom_bad_response', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'wpcom_bad_response'.

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...
1011
		}
1012
1013
		// Jetpack ID error block.
1014
		if ( empty( $registration_response->jetpack_id ) ) {
1015
			return new \WP_Error(
1016
				'jetpack_id',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'jetpack_id'.

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...
1017
				/* translators: %s is an error message string */
1018
				sprintf( __( 'Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack' ), $entity ),
1019
				$entity
1020
			);
1021
		} elseif ( ! is_scalar( $registration_response->jetpack_id ) ) {
1022
			return new \WP_Error(
1023
				'jetpack_id',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'jetpack_id'.

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...
1024
				/* translators: %s is an error message string */
1025
				sprintf( __( 'Error Details: Jetpack ID is not a scalar. Do not publicly post this error message! %s', 'jetpack' ), $entity ),
1026
				$entity
1027
			);
1028 View Code Duplication
		} elseif ( preg_match( '/[^0-9]/', $registration_response->jetpack_id ) ) {
1029
			return new \WP_Error(
1030
				'jetpack_id',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'jetpack_id'.

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...
1031
				/* translators: %s is an error message string */
1032
				sprintf( __( 'Error Details: Jetpack ID begins with a numeral. Do not publicly post this error message! %s', 'jetpack' ), $entity ),
1033
				$entity
1034
			);
1035
		}
1036
1037
		return $registration_response;
1038
	}
1039
1040
	/**
1041
	 * Adds a used nonce to a list of known nonces.
1042
	 *
1043
	 * @param int    $timestamp the current request timestamp.
1044
	 * @param string $nonce the nonce value.
1045
	 * @return bool whether the nonce is unique or not.
1046
	 */
1047
	public function add_nonce( $timestamp, $nonce ) {
1048
		global $wpdb;
1049
		static $nonces_used_this_request = array();
1050
1051
		if ( isset( $nonces_used_this_request[ "$timestamp:$nonce" ] ) ) {
1052
			return $nonces_used_this_request[ "$timestamp:$nonce" ];
1053
		}
1054
1055
		// This should always have gone through Jetpack_Signature::sign_request() first to check $timestamp an $nonce.
1056
		$timestamp = (int) $timestamp;
1057
		$nonce     = esc_sql( $nonce );
1058
1059
		// Raw query so we can avoid races: add_option will also update.
1060
		$show_errors = $wpdb->show_errors( false );
1061
1062
		$old_nonce = $wpdb->get_row(
1063
			$wpdb->prepare( "SELECT * FROM `$wpdb->options` WHERE option_name = %s", "jetpack_nonce_{$timestamp}_{$nonce}" )
1064
		);
1065
1066
		if ( is_null( $old_nonce ) ) {
1067
			$return = $wpdb->query(
1068
				$wpdb->prepare(
1069
					"INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, %s)",
1070
					"jetpack_nonce_{$timestamp}_{$nonce}",
1071
					time(),
1072
					'no'
1073
				)
1074
			);
1075
		} else {
1076
			$return = false;
1077
		}
1078
1079
		$wpdb->show_errors( $show_errors );
1080
1081
		$nonces_used_this_request[ "$timestamp:$nonce" ] = $return;
1082
1083
		return $return;
1084
	}
1085
1086
	/**
1087
	 * Cleans nonces that were saved when calling ::add_nonce.
1088
	 *
1089
	 * @todo Properly prepare the query before executing it.
1090
	 *
1091
	 * @param bool $all whether to clean even non-expired nonces.
1092
	 */
1093
	public function clean_nonces( $all = false ) {
1094
		global $wpdb;
1095
1096
		$sql      = "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE %s";
1097
		$sql_args = array( $wpdb->esc_like( 'jetpack_nonce_' ) . '%' );
1098
1099
		if ( true !== $all ) {
1100
			$sql       .= ' AND CAST( `option_value` AS UNSIGNED ) < %d';
1101
			$sql_args[] = time() - 3600;
1102
		}
1103
1104
		$sql .= ' ORDER BY `option_id` LIMIT 100';
1105
1106
		$sql = $wpdb->prepare( $sql, $sql_args ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1107
1108
		for ( $i = 0; $i < 1000; $i++ ) {
1109
			if ( ! $wpdb->query( $sql ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1110
				break;
1111
			}
1112
		}
1113
	}
1114
1115
	/**
1116
	 * Builds the timeout limit for queries talking with the wpcom servers.
1117
	 *
1118
	 * Based on local php max_execution_time in php.ini
1119
	 *
1120
	 * @since 5.4
1121
	 * @return int
1122
	 **/
1123
	public function get_max_execution_time() {
1124
		$timeout = (int) ini_get( 'max_execution_time' );
1125
1126
		// Ensure exec time set in php.ini.
1127
		if ( ! $timeout ) {
1128
			$timeout = 30;
1129
		}
1130
		return $timeout;
1131
	}
1132
1133
	/**
1134
	 * Sets a minimum request timeout, and returns the current timeout
1135
	 *
1136
	 * @since 5.4
1137
	 * @param Integer $min_timeout the minimum timeout value.
1138
	 **/
1139 View Code Duplication
	public function set_min_time_limit( $min_timeout ) {
1140
		$timeout = $this->get_max_execution_time();
1141
		if ( $timeout < $min_timeout ) {
1142
			$timeout = $min_timeout;
1143
			set_time_limit( $timeout );
1144
		}
1145
		return $timeout;
1146
	}
1147
1148
	/**
1149
	 * Get our assumed site creation date.
1150
	 * Calculated based on the earlier date of either:
1151
	 * - Earliest admin user registration date.
1152
	 * - Earliest date of post of any post type.
1153
	 *
1154
	 * @since 7.2.0
1155
	 *
1156
	 * @return string Assumed site creation date and time.
1157
	 */
1158
	public function get_assumed_site_creation_date() {
1159
		$cached_date = get_transient( 'jetpack_assumed_site_creation_date' );
1160
		if ( ! empty( $cached_date ) ) {
1161
			return $cached_date;
1162
		}
1163
1164
		$earliest_registered_users  = get_users(
1165
			array(
1166
				'role'    => 'administrator',
1167
				'orderby' => 'user_registered',
1168
				'order'   => 'ASC',
1169
				'fields'  => array( 'user_registered' ),
1170
				'number'  => 1,
1171
			)
1172
		);
1173
		$earliest_registration_date = $earliest_registered_users[0]->user_registered;
1174
1175
		$earliest_posts = get_posts(
1176
			array(
1177
				'posts_per_page' => 1,
1178
				'post_type'      => 'any',
1179
				'post_status'    => 'any',
1180
				'orderby'        => 'date',
1181
				'order'          => 'ASC',
1182
			)
1183
		);
1184
1185
		// If there are no posts at all, we'll count only on user registration date.
1186
		if ( $earliest_posts ) {
1187
			$earliest_post_date = $earliest_posts[0]->post_date;
1188
		} else {
1189
			$earliest_post_date = PHP_INT_MAX;
1190
		}
1191
1192
		$assumed_date = min( $earliest_registration_date, $earliest_post_date );
1193
		set_transient( 'jetpack_assumed_site_creation_date', $assumed_date );
1194
1195
		return $assumed_date;
1196
	}
1197
1198
	/**
1199
	 * Adds the activation source string as a parameter to passed arguments.
1200
	 *
1201
	 * @todo Refactor to use rawurlencode() instead of urlencode().
1202
	 *
1203
	 * @param array $args arguments that need to have the source added.
1204
	 * @return array $amended arguments.
1205
	 */
1206 View Code Duplication
	public static function apply_activation_source_to_args( $args ) {
1207
		list( $activation_source_name, $activation_source_keyword ) = get_option( 'jetpack_activation_source' );
1208
1209
		if ( $activation_source_name ) {
1210
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.urlencode_urlencode
1211
			$args['_as'] = urlencode( $activation_source_name );
1212
		}
1213
1214
		if ( $activation_source_keyword ) {
1215
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.urlencode_urlencode
1216
			$args['_ak'] = urlencode( $activation_source_keyword );
1217
		}
1218
1219
		return $args;
1220
	}
1221
1222
	/**
1223
	 * Returns the callable that would be used to generate secrets.
1224
	 *
1225
	 * @return Callable a function that returns a secure string to be used as a secret.
1226
	 */
1227
	protected function get_secret_callable() {
1228
		if ( ! isset( $this->secret_callable ) ) {
1229
			/**
1230
			 * Allows modification of the callable that is used to generate connection secrets.
1231
			 *
1232
			 * @param Callable a function or method that returns a secret string.
1233
			 */
1234
			$this->secret_callable = apply_filters( 'jetpack_connection_secret_generator', array( $this, 'secret_callable_method' ) );
1235
		}
1236
1237
		return $this->secret_callable;
1238
	}
1239
1240
	/**
1241
	 * Runs the wp_generate_password function with the required parameters. This is the
1242
	 * default implementation of the secret callable, can be overridden using the
1243
	 * jetpack_connection_secret_generator filter.
1244
	 *
1245
	 * @return String $secret value.
1246
	 */
1247
	private function secret_callable_method() {
1248
		return wp_generate_password( 32, false );
1249
	}
1250
1251
	/**
1252
	 * Generates two secret tokens and the end of life timestamp for them.
1253
	 *
1254
	 * @param String  $action  The action name.
1255
	 * @param Integer $user_id The user identifier.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user_id not be false|integer?

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...
1256
	 * @param Integer $exp     Expiration time in seconds.
1257
	 */
1258
	public function generate_secrets( $action, $user_id = false, $exp = 600 ) {
1259
		if ( false === $user_id ) {
1260
			$user_id = get_current_user_id();
1261
		}
1262
1263
		$callable = $this->get_secret_callable();
1264
1265
		$secrets = \Jetpack_Options::get_raw_option(
1266
			self::SECRETS_OPTION_NAME,
1267
			array()
1268
		);
1269
1270
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
1271
1272
		if (
1273
			isset( $secrets[ $secret_name ] ) &&
1274
			$secrets[ $secret_name ]['exp'] > time()
1275
		) {
1276
			return $secrets[ $secret_name ];
1277
		}
1278
1279
		$secret_value = array(
1280
			'secret_1' => call_user_func( $callable ),
1281
			'secret_2' => call_user_func( $callable ),
1282
			'exp'      => time() + $exp,
1283
		);
1284
1285
		$secrets[ $secret_name ] = $secret_value;
1286
1287
		\Jetpack_Options::update_raw_option( self::SECRETS_OPTION_NAME, $secrets );
1288
		return $secrets[ $secret_name ];
1289
	}
1290
1291
	/**
1292
	 * Returns two secret tokens and the end of life timestamp for them.
1293
	 *
1294
	 * @param String  $action  The action name.
1295
	 * @param Integer $user_id The user identifier.
1296
	 * @return string|array an array of secrets or an error string.
1297
	 */
1298
	public function get_secrets( $action, $user_id ) {
1299
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
1300
		$secrets     = \Jetpack_Options::get_raw_option(
1301
			self::SECRETS_OPTION_NAME,
1302
			array()
1303
		);
1304
1305
		if ( ! isset( $secrets[ $secret_name ] ) ) {
1306
			return self::SECRETS_MISSING;
1307
		}
1308
1309
		if ( $secrets[ $secret_name ]['exp'] < time() ) {
1310
			$this->delete_secrets( $action, $user_id );
1311
			return self::SECRETS_EXPIRED;
1312
		}
1313
1314
		return $secrets[ $secret_name ];
1315
	}
1316
1317
	/**
1318
	 * Deletes secret tokens in case they, for example, have expired.
1319
	 *
1320
	 * @param String  $action  The action name.
1321
	 * @param Integer $user_id The user identifier.
1322
	 */
1323
	public function delete_secrets( $action, $user_id ) {
1324
		$secret_name = 'jetpack_' . $action . '_' . $user_id;
1325
		$secrets     = \Jetpack_Options::get_raw_option(
1326
			self::SECRETS_OPTION_NAME,
1327
			array()
1328
		);
1329
		if ( isset( $secrets[ $secret_name ] ) ) {
1330
			unset( $secrets[ $secret_name ] );
1331
			\Jetpack_Options::update_raw_option( self::SECRETS_OPTION_NAME, $secrets );
1332
		}
1333
	}
1334
1335
	/**
1336
	 * Deletes all connection tokens and transients from the local Jetpack site.
1337
	 */
1338
	public function delete_all_connection_tokens() {
1339
		\Jetpack_Options::delete_option(
1340
			array(
1341
				'blog_token',
1342
				'user_token',
1343
				'user_tokens',
1344
				'master_user',
1345
				'time_diff',
1346
				'fallback_no_verify_ssl_certs',
1347
			)
1348
		);
1349
1350
		\Jetpack_Options::delete_raw_option( 'jetpack_secrets' );
1351
1352
		// Delete cached connected user data.
1353
		$transient_key = 'jetpack_connected_user_data_' . get_current_user_id();
1354
		delete_transient( $transient_key );
1355
	}
1356
1357
	/**
1358
	 * Tells WordPress.com to disconnect the site and clear all tokens from cached site.
1359
	 */
1360
	public function disconnect_site_wpcom() {
1361
		$xml = new \Jetpack_IXR_Client();
1362
		$xml->query( 'jetpack.deregister', get_current_user_id() );
1363
	}
1364
1365
	/**
1366
	 * Responds to a WordPress.com call to register the current site.
1367
	 * Should be changed to protected.
1368
	 *
1369
	 * @param array $registration_data Array of [ secret_1, user_id ].
1370
	 */
1371
	public function handle_registration( array $registration_data ) {
1372
		list( $registration_secret_1, $registration_user_id ) = $registration_data;
1373
		if ( empty( $registration_user_id ) ) {
1374
			return new \WP_Error( 'registration_state_invalid', __( 'Invalid Registration State', 'jetpack' ), 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'registration_state_invalid'.

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...
1375
		}
1376
1377
		return $this->verify_secrets( 'register', $registration_secret_1, (int) $registration_user_id );
1378
	}
1379
1380
	/**
1381
	 * Verify a Previously Generated Secret.
1382
	 *
1383
	 * @param string $action   The type of secret to verify.
1384
	 * @param string $secret_1 The secret string to compare to what is stored.
1385
	 * @param int    $user_id  The user ID of the owner of the secret.
1386
	 * @return \WP_Error|string WP_Error on failure, secret_2 on success.
1387
	 */
1388
	public function verify_secrets( $action, $secret_1, $user_id ) {
1389
		$allowed_actions = array( 'register', 'authorize', 'publicize' );
1390
		if ( ! in_array( $action, $allowed_actions, true ) ) {
1391
			return new \WP_Error( 'unknown_verification_action', 'Unknown Verification Action', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'unknown_verification_action'.

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...
1392
		}
1393
1394
		$user = get_user_by( 'id', $user_id );
1395
1396
		/**
1397
		 * We've begun verifying the previously generated secret.
1398
		 *
1399
		 * @since 7.5.0
1400
		 *
1401
		 * @param string   $action The type of secret to verify.
1402
		 * @param \WP_User $user The user object.
1403
		 */
1404
		do_action( 'jetpack_verify_secrets_begin', $action, $user );
1405
1406
		$return_error = function( \WP_Error $error ) use ( $action, $user ) {
1407
			/**
1408
			 * Verifying of the previously generated secret has failed.
1409
			 *
1410
			 * @since 7.5.0
1411
			 *
1412
			 * @param string    $action  The type of secret to verify.
1413
			 * @param \WP_User  $user The user object.
1414
			 * @param \WP_Error $error The error object.
1415
			 */
1416
			do_action( 'jetpack_verify_secrets_fail', $action, $user, $error );
1417
1418
			return $error;
1419
		};
1420
1421
		$stored_secrets = $this->get_secrets( $action, $user_id );
1422
		$this->delete_secrets( $action, $user_id );
1423
1424
		$error = null;
1425
		if ( empty( $secret_1 ) ) {
1426
			$error = $return_error(
1427
				new \WP_Error(
1428
					'verify_secret_1_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secret_1_missing'.

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

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

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

Loading history...
1429
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
1430
					sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'secret_1' ),
1431
					400
1432
				)
1433
			);
1434
		} elseif ( ! is_string( $secret_1 ) ) {
1435
			$error = $return_error(
1436
				new \WP_Error(
1437
					'verify_secret_1_malformed',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secret_1_malformed'.

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

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

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

Loading history...
1438
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
1439
					sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'secret_1' ),
1440
					400
1441
				)
1442
			);
1443
		} elseif ( empty( $user_id ) ) {
1444
			// $user_id is passed around during registration as "state".
1445
			$error = $return_error(
1446
				new \WP_Error(
1447
					'state_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'state_missing'.

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

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

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

Loading history...
1448
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
1449
					sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'state' ),
1450
					400
1451
				)
1452
			);
1453
		} elseif ( ! ctype_digit( (string) $user_id ) ) {
1454
			$error = $return_error(
1455
				new \WP_Error(
1456
					'state_malformed',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'state_malformed'.

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

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

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

Loading history...
1457
					/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
1458
					sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'state' ),
1459
					400
1460
				)
1461
			);
1462
		} elseif ( self::SECRETS_MISSING === $stored_secrets ) {
1463
			$error = $return_error(
1464
				new \WP_Error(
1465
					'verify_secrets_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_missing'.

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

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

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

Loading history...
1466
					__( 'Verification secrets not found', 'jetpack' ),
1467
					400
1468
				)
1469
			);
1470
		} elseif ( self::SECRETS_EXPIRED === $stored_secrets ) {
1471
			$error = $return_error(
1472
				new \WP_Error(
1473
					'verify_secrets_expired',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_expired'.

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...
1474
					__( 'Verification took too long', 'jetpack' ),
1475
					400
1476
				)
1477
			);
1478
		} elseif ( ! $stored_secrets ) {
1479
			$error = $return_error(
1480
				new \WP_Error(
1481
					'verify_secrets_empty',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_empty'.

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...
1482
					__( 'Verification secrets are empty', 'jetpack' ),
1483
					400
1484
				)
1485
			);
1486
		} elseif ( is_wp_error( $stored_secrets ) ) {
1487
			$stored_secrets->add_data( 400 );
0 ignored issues
show
Bug introduced by
The method add_data cannot be called on $stored_secrets (of type string|array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1488
			$error = $return_error( $stored_secrets );
1489
		} elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) {
1490
			$error = $return_error(
1491
				new \WP_Error(
1492
					'verify_secrets_incomplete',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_incomplete'.

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

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

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

Loading history...
1493
					__( 'Verification secrets are incomplete', 'jetpack' ),
1494
					400
1495
				)
1496
			);
1497
		} elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) {
1498
			$error = $return_error(
1499
				new \WP_Error(
1500
					'verify_secrets_mismatch',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'verify_secrets_mismatch'.

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

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

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

Loading history...
1501
					__( 'Secret mismatch', 'jetpack' ),
1502
					400
1503
				)
1504
			);
1505
		}
1506
1507
		// Something went wrong during the checks, returning the error.
1508
		if ( ! empty( $error ) ) {
1509
			return $error;
1510
		}
1511
1512
		/**
1513
		 * We've succeeded at verifying the previously generated secret.
1514
		 *
1515
		 * @since 7.5.0
1516
		 *
1517
		 * @param string   $action The type of secret to verify.
1518
		 * @param \WP_User $user The user object.
1519
		 */
1520
		do_action( 'jetpack_verify_secrets_success', $action, $user );
1521
1522
		return $stored_secrets['secret_2'];
1523
	}
1524
1525
	/**
1526
	 * Responds to a WordPress.com call to authorize the current user.
1527
	 * Should be changed to protected.
1528
	 */
1529
	public function handle_authorization() {
1530
1531
	}
1532
1533
	/**
1534
	 * Obtains the auth token.
1535
	 *
1536
	 * @param array $data The request data.
1537
	 * @return object|\WP_Error Returns the auth token on success.
1538
	 *                          Returns a \WP_Error on failure.
1539
	 */
1540
	public function get_token( $data ) {
1541
		$roles = new Roles();
1542
		$role  = $roles->translate_current_user_to_role();
1543
1544
		if ( ! $role ) {
1545
			return new \WP_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'role'.

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...
1546
		}
1547
1548
		$client_secret = $this->get_access_token();
1549
		if ( ! $client_secret ) {
1550
			return new \WP_Error( 'client_secret', __( 'You need to register your Jetpack before connecting it.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'client_secret'.

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...
1551
		}
1552
1553
		/**
1554
		 * Filter the URL of the first time the user gets redirected back to your site for connection
1555
		 * data processing.
1556
		 *
1557
		 * @since 8.0.0
1558
		 *
1559
		 * @param string $redirect_url Defaults to the site admin URL.
1560
		 */
1561
		$processing_url = apply_filters( 'jetpack_token_processing_url', admin_url( 'admin.php' ) );
1562
1563
		$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
1564
1565
		/**
1566
		* Filter the URL to redirect the user back to when the authentication process
1567
		* is complete.
1568
		*
1569
		* @since 8.0.0
1570
		*
1571
		* @param string $redirect_url Defaults to the site URL.
1572
		*/
1573
		$redirect = apply_filters( 'jetpack_token_redirect_url', $redirect );
1574
1575
		$redirect_uri = ( 'calypso' === $data['auth_type'] )
1576
			? $data['redirect_uri']
1577
			: add_query_arg(
1578
				array(
1579
					'action'   => 'authorize',
1580
					'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
1581
					'redirect' => $redirect ? rawurlencode( $redirect ) : false,
1582
				),
1583
				esc_url( $processing_url )
1584
			);
1585
1586
		/**
1587
		 * Filters the token request data.
1588
		 *
1589
		 * @since 8.0.0
1590
		 *
1591
		 * @param array $request_data request data.
1592
		 */
1593
		$body = apply_filters(
1594
			'jetpack_token_request_body',
1595
			array(
1596
				'client_id'     => \Jetpack_Options::get_option( 'id' ),
1597
				'client_secret' => $client_secret->secret,
1598
				'grant_type'    => 'authorization_code',
1599
				'code'          => $data['code'],
1600
				'redirect_uri'  => $redirect_uri,
1601
			)
1602
		);
1603
1604
		$args = array(
1605
			'method'  => 'POST',
1606
			'body'    => $body,
1607
			'headers' => array(
1608
				'Accept' => 'application/json',
1609
			),
1610
		);
1611
1612
		add_filter( 'http_request_timeout', array( $this, 'increase_timeout' ), PHP_INT_MAX - 1 );
1613
		$response = Client::_wp_remote_request( Utils::fix_url_for_bad_hosts( $this->api_url( 'token' ) ), $args );
1614
		remove_filter( 'http_request_timeout', array( $this, 'increase_timeout' ), PHP_INT_MAX - 1 );
1615
1616
		if ( is_wp_error( $response ) ) {
1617
			return new \WP_Error( 'token_http_request_failed', $response->get_error_message() );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'token_http_request_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...
1618
		}
1619
1620
		$code   = wp_remote_retrieve_response_code( $response );
1621
		$entity = wp_remote_retrieve_body( $response );
1622
1623
		if ( $entity ) {
1624
			$json = json_decode( $entity );
1625
		} else {
1626
			$json = false;
1627
		}
1628
1629
		if ( 200 !== $code || ! empty( $json->error ) ) {
1630
			if ( empty( $json->error ) ) {
1631
				return new \WP_Error( 'unknown', '', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with '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...
1632
			}
1633
1634
			/* translators: Error description string. */
1635
			$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
1636
1637
			return new \WP_Error( (string) $json->error, $error_description, $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with (string) $json->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...
1638
		}
1639
1640
		if ( empty( $json->access_token ) || ! is_scalar( $json->access_token ) ) {
1641
			return new \WP_Error( 'access_token', '', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'access_token'.

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...
1642
		}
1643
1644
		if ( empty( $json->token_type ) || 'X_JETPACK' !== strtoupper( $json->token_type ) ) {
1645
			return new \WP_Error( 'token_type', '', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'token_type'.

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...
1646
		}
1647
1648
		if ( empty( $json->scope ) ) {
1649
			return new \WP_Error( 'scope', 'No Scope', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
1650
		}
1651
1652
		// TODO: get rid of the error silencer.
1653
		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1654
		@list( $role, $hmac ) = explode( ':', $json->scope );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1655
		if ( empty( $role ) || empty( $hmac ) ) {
1656
			return new \WP_Error( 'scope', 'Malformed Scope', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
1657
		}
1658
1659
		if ( $this->sign_role( $role ) !== $json->scope ) {
1660
			return new \WP_Error( 'scope', 'Invalid Scope', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
1661
		}
1662
1663
		$cap = $roles->translate_role_to_cap( $role );
1664
		if ( ! $cap ) {
1665
			return new \WP_Error( 'scope', 'No Cap', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
1666
		}
1667
1668
		if ( ! current_user_can( $cap ) ) {
1669
			return new \WP_Error( 'scope', 'current_user_cannot', $code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'scope'.

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...
1670
		}
1671
1672
		/**
1673
		 * Fires after user has successfully received an auth token.
1674
		 *
1675
		 * @since 3.9.0
1676
		 */
1677
		do_action( 'jetpack_user_authorized' );
1678
1679
		return (string) $json->access_token;
1680
	}
1681
1682
	/**
1683
	 * Increases the request timeout value to 30 seconds.
1684
	 *
1685
	 * @return int Returns 30.
1686
	 */
1687
	public function increase_timeout() {
1688
		return 30;
1689
	}
1690
1691
	/**
1692
	 * Builds a URL to the Jetpack connection auth page.
1693
	 *
1694
	 * @param WP_User $user (optional) defaults to the current logged in user.
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...
1695
	 * @param String  $redirect (optional) a redirect URL to use instead of the default.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $redirect 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...
1696
	 * @return string Connect URL.
1697
	 */
1698
	public function get_authorization_url( $user = null, $redirect = null ) {
1699
1700
		if ( empty( $user ) ) {
1701
			$user = wp_get_current_user();
1702
		}
1703
1704
		$roles       = new Roles();
1705
		$role        = $roles->translate_user_to_role( $user );
1706
		$signed_role = $this->sign_role( $role );
1707
1708
		/**
1709
		 * Filter the URL of the first time the user gets redirected back to your site for connection
1710
		 * data processing.
1711
		 *
1712
		 * @since 8.0.0
1713
		 *
1714
		 * @param string $redirect_url Defaults to the site admin URL.
1715
		 */
1716
		$processing_url = apply_filters( 'jetpack_connect_processing_url', admin_url( 'admin.php' ) );
1717
1718
		/**
1719
		 * Filter the URL to redirect the user back to when the authorization process
1720
		 * is complete.
1721
		 *
1722
		 * @since 8.0.0
1723
		 *
1724
		 * @param string $redirect_url Defaults to the site URL.
1725
		 */
1726
		$redirect = apply_filters( 'jetpack_connect_redirect_url', $redirect );
1727
1728
		$secrets = $this->generate_secrets( 'authorize', $user->ID, 2 * HOUR_IN_SECONDS );
1729
1730
		/**
1731
		 * Filter the type of authorization.
1732
		 * 'calypso' completes authorization on wordpress.com/jetpack/connect
1733
		 * while 'jetpack' ( or any other value ) completes the authorization at jetpack.wordpress.com.
1734
		 *
1735
		 * @since 4.3.3
1736
		 *
1737
		 * @param string $auth_type Defaults to 'calypso', can also be 'jetpack'.
1738
		 */
1739
		$auth_type = apply_filters( 'jetpack_auth_type', 'calypso' );
1740
1741
		/**
1742
		 * Filters the user connection request data for additional property addition.
1743
		 *
1744
		 * @since 8.0.0
1745
		 *
1746
		 * @param array $request_data request data.
1747
		 */
1748
		$body = apply_filters(
1749
			'jetpack_connect_request_body',
1750
			array(
1751
				'response_type' => 'code',
1752
				'client_id'     => \Jetpack_Options::get_option( 'id' ),
1753
				'redirect_uri'  => add_query_arg(
1754
					array(
1755
						'action'   => 'authorize',
1756
						'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
1757
						'redirect' => rawurlencode( $redirect ),
1758
					),
1759
					esc_url( $processing_url )
1760
				),
1761
				'state'         => $user->ID,
1762
				'scope'         => $signed_role,
1763
				'user_email'    => $user->user_email,
1764
				'user_login'    => $user->user_login,
1765
				'is_active'     => $this->is_active(),
1766
				'jp_version'    => Constants::get_constant( 'JETPACK__VERSION' ),
1767
				'auth_type'     => $auth_type,
1768
				'secret'        => $secrets['secret_1'],
1769
				'blogname'      => get_option( 'blogname' ),
1770
				'site_url'      => site_url(),
1771
				'home_url'      => home_url(),
1772
				'site_icon'     => get_site_icon_url(),
1773
				'site_lang'     => get_locale(),
1774
				'site_created'  => $this->get_assumed_site_creation_date(),
1775
			)
1776
		);
1777
1778
		$body = $this->apply_activation_source_to_args( urlencode_deep( $body ) );
1779
1780
		$api_url = $this->api_url( 'authorize' );
1781
1782
		return add_query_arg( $body, $api_url );
1783
	}
1784
1785
	/**
1786
	 * Authorizes the user by obtaining and storing the user token.
1787
	 *
1788
	 * @param array $data The request data.
1789
	 * @return string|\WP_Error Returns a string on success.
1790
	 *                          Returns a \WP_Error on failure.
1791
	 */
1792
	public function authorize( $data = array() ) {
1793
		/**
1794
		 * Action fired when user authorization starts.
1795
		 *
1796
		 * @since 8.0.0
1797
		 */
1798
		do_action( 'jetpack_authorize_starting' );
1799
1800
		$roles = new Roles();
1801
		$role  = $roles->translate_current_user_to_role();
1802
1803
		if ( ! $role ) {
1804
			return new \WP_Error( 'no_role', 'Invalid request.', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_role'.

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...
1805
		}
1806
1807
		$cap = $roles->translate_role_to_cap( $role );
1808
		if ( ! $cap ) {
1809
			return new \WP_Error( 'no_cap', 'Invalid request.', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_cap'.

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...
1810
		}
1811
1812
		if ( ! empty( $data['error'] ) ) {
1813
			return new \WP_Error( $data['error'], 'Error included in the request.', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $data['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...
1814
		}
1815
1816
		if ( ! isset( $data['state'] ) ) {
1817
			return new \WP_Error( 'no_state', 'Request must include state.', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_state'.

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...
1818
		}
1819
1820
		if ( ! ctype_digit( $data['state'] ) ) {
1821
			return new \WP_Error( $data['error'], 'State must be an integer.', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $data['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...
1822
		}
1823
1824
		$current_user_id = get_current_user_id();
1825
		if ( $current_user_id !== (int) $data['state'] ) {
1826
			return new \WP_Error( 'wrong_state', 'State does not match current user.', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'wrong_state'.

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...
1827
		}
1828
1829
		if ( empty( $data['code'] ) ) {
1830
			return new \WP_Error( 'no_code', 'Request must include an authorization code.', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_code'.

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

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

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

Loading history...
1831
		}
1832
1833
		$token = $this->get_token( $data );
1834
1835 View Code Duplication
		if ( is_wp_error( $token ) ) {
1836
			$code = $token->get_error_code();
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

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

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

Loading history...
1837
			if ( empty( $code ) ) {
1838
				$code = 'invalid_token';
1839
			}
1840
			return new \WP_Error( $code, $token->get_error_message(), 400 );
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

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

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

Loading history...
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $code.

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

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

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

Loading history...
1841
		}
1842
1843
		if ( ! $token ) {
1844
			return new \WP_Error( 'no_token', 'Error generating token.', 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_token'.

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...
1845
		}
1846
1847
		$is_master_user = ! $this->is_active();
1848
1849
		Utils::update_user_token( $current_user_id, sprintf( '%s.%d', $token, $current_user_id ), $is_master_user );
1850
1851
		if ( ! $is_master_user ) {
1852
			/**
1853
			 * Action fired when a secondary user has been authorized.
1854
			 *
1855
			 * @since 8.0.0
1856
			 */
1857
			do_action( 'jetpack_authorize_ending_linked' );
1858
			return 'linked';
1859
		}
1860
1861
		/**
1862
		 * Action fired when the master user has been authorized.
1863
		 *
1864
		 * @since 8.0.0
1865
		 *
1866
		 * @param array $data The request data.
1867
		 */
1868
		do_action( 'jetpack_authorize_ending_authorized', $data );
1869
1870
		\Jetpack_Options::delete_raw_option( 'jetpack_last_connect_url_check' );
1871
1872
		// Start nonce cleaner.
1873
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
1874
		wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
1875
1876
		return 'authorized';
1877
	}
1878
1879
	/**
1880
	 * Disconnects from the Jetpack servers.
1881
	 * Forgets all connection details and tells the Jetpack servers to do the same.
1882
	 */
1883
	public function disconnect_site() {
1884
1885
	}
1886
1887
	/**
1888
	 * The Base64 Encoding of the SHA1 Hash of the Input.
1889
	 *
1890
	 * @param string $text The string to hash.
1891
	 * @return string
1892
	 */
1893
	public function sha1_base64( $text ) {
1894
		return base64_encode( sha1( $text, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
1895
	}
1896
1897
	/**
1898
	 * This function mirrors Jetpack_Data::is_usable_domain() in the WPCOM codebase.
1899
	 *
1900
	 * @param string $domain The domain to check.
1901
	 *
1902
	 * @return bool|WP_Error
1903
	 */
1904
	public function is_usable_domain( $domain ) {
1905
1906
		// If it's empty, just fail out.
1907
		if ( ! $domain ) {
1908
			return new \WP_Error(
1909
				'fail_domain_empty',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'fail_domain_empty'.

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...
1910
				/* translators: %1$s is a domain name. */
1911
				sprintf( __( 'Domain `%1$s` just failed is_usable_domain check as it is empty.', 'jetpack' ), $domain )
1912
			);
1913
		}
1914
1915
		/**
1916
		 * Skips the usuable domain check when connecting a site.
1917
		 *
1918
		 * Allows site administrators with domains that fail gethostname-based checks to pass the request to WP.com
1919
		 *
1920
		 * @since 4.1.0
1921
		 *
1922
		 * @param bool If the check should be skipped. Default false.
1923
		 */
1924
		if ( apply_filters( 'jetpack_skip_usuable_domain_check', false ) ) {
1925
			return true;
1926
		}
1927
1928
		// None of the explicit localhosts.
1929
		$forbidden_domains = array(
1930
			'wordpress.com',
1931
			'localhost',
1932
			'localhost.localdomain',
1933
			'127.0.0.1',
1934
			'local.wordpress.test',         // VVV pattern.
1935
			'local.wordpress-trunk.test',   // VVV pattern.
1936
			'src.wordpress-develop.test',   // VVV pattern.
1937
			'build.wordpress-develop.test', // VVV pattern.
1938
		);
1939 View Code Duplication
		if ( in_array( $domain, $forbidden_domains, true ) ) {
1940
			return new \WP_Error(
1941
				'fail_domain_forbidden',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'fail_domain_forbidden'.

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...
1942
				sprintf(
1943
					/* translators: %1$s is a domain name. */
1944
					__(
1945
						'Domain `%1$s` just failed is_usable_domain check as it is in the forbidden array.',
1946
						'jetpack'
1947
					),
1948
					$domain
1949
				)
1950
			);
1951
		}
1952
1953
		// No .test or .local domains.
1954 View Code Duplication
		if ( preg_match( '#\.(test|local)$#i', $domain ) ) {
1955
			return new \WP_Error(
1956
				'fail_domain_tld',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'fail_domain_tld'.

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...
1957
				sprintf(
1958
					/* translators: %1$s is a domain name. */
1959
					__(
1960
						'Domain `%1$s` just failed is_usable_domain check as it uses an invalid top level domain.',
1961
						'jetpack'
1962
					),
1963
					$domain
1964
				)
1965
			);
1966
		}
1967
1968
		// No WPCOM subdomains.
1969 View Code Duplication
		if ( preg_match( '#\.WordPress\.com$#i', $domain ) ) {
1970
			return new \WP_Error(
1971
				'fail_subdomain_wpcom',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'fail_subdomain_wpcom'.

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...
1972
				sprintf(
1973
					/* translators: %1$s is a domain name. */
1974
					__(
1975
						'Domain `%1$s` just failed is_usable_domain check as it is a subdomain of WordPress.com.',
1976
						'jetpack'
1977
					),
1978
					$domain
1979
				)
1980
			);
1981
		}
1982
1983
		// If PHP was compiled without support for the Filter module (very edge case).
1984
		if ( ! function_exists( 'filter_var' ) ) {
1985
			// Just pass back true for now, and let wpcom sort it out.
1986
			return true;
1987
		}
1988
1989
		return true;
1990
	}
1991
1992
	/**
1993
	 * Gets the requested token.
1994
	 *
1995
	 * Tokens are one of two types:
1996
	 * 1. Blog Tokens: These are the "main" tokens. Each site typically has one Blog Token,
1997
	 *    though some sites can have multiple "Special" Blog Tokens (see below). These tokens
1998
	 *    are not associated with a user account. They represent the site's connection with
1999
	 *    the Jetpack servers.
2000
	 * 2. User Tokens: These are "sub-"tokens. Each connected user account has one User Token.
2001
	 *
2002
	 * All tokens look like "{$token_key}.{$private}". $token_key is a public ID for the
2003
	 * token, and $private is a secret that should never be displayed anywhere or sent
2004
	 * over the network; it's used only for signing things.
2005
	 *
2006
	 * Blog Tokens can be "Normal" or "Special".
2007
	 * * Normal: The result of a normal connection flow. They look like
2008
	 *   "{$random_string_1}.{$random_string_2}"
2009
	 *   That is, $token_key and $private are both random strings.
2010
	 *   Sites only have one Normal Blog Token. Normal Tokens are found in either
2011
	 *   Jetpack_Options::get_option( 'blog_token' ) (usual) or the JETPACK_BLOG_TOKEN
2012
	 *   constant (rare).
2013
	 * * Special: A connection token for sites that have gone through an alternative
2014
	 *   connection flow. They look like:
2015
	 *   ";{$special_id}{$special_version};{$wpcom_blog_id};.{$random_string}"
2016
	 *   That is, $private is a random string and $token_key has a special structure with
2017
	 *   lots of semicolons.
2018
	 *   Most sites have zero Special Blog Tokens. Special tokens are only found in the
2019
	 *   JETPACK_BLOG_TOKEN constant.
2020
	 *
2021
	 * In particular, note that Normal Blog Tokens never start with ";" and that
2022
	 * Special Blog Tokens always do.
2023
	 *
2024
	 * When searching for a matching Blog Tokens, Blog Tokens are examined in the following
2025
	 * order:
2026
	 * 1. Defined Special Blog Tokens (via the JETPACK_BLOG_TOKEN constant)
2027
	 * 2. Stored Normal Tokens (via Jetpack_Options::get_option( 'blog_token' ))
2028
	 * 3. Defined Normal Tokens (via the JETPACK_BLOG_TOKEN constant)
2029
	 *
2030
	 * @param int|false    $user_id   false: Return the Blog Token. int: Return that user's User Token.
2031
	 * @param string|false $token_key If provided, check that the token matches the provided input.
2032
	 * @param bool|true    $suppress_errors If true, return a falsy value when the token isn't found; When false, return a descriptive WP_Error when the token isn't found.
2033
	 *
2034
	 * @return object|false
2035
	 */
2036
	public function get_access_token( $user_id = false, $token_key = false, $suppress_errors = true ) {
2037
		$possible_special_tokens = array();
2038
		$possible_normal_tokens  = array();
2039
		$user_tokens             = \Jetpack_Options::get_option( 'user_tokens' );
2040
2041
		if ( $user_id ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user_id of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2042
			if ( ! $user_tokens ) {
2043
				return $suppress_errors ? false : new \WP_Error( 'no_user_tokens' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_user_tokens'.

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...
2044
			}
2045
			if ( self::JETPACK_MASTER_USER === $user_id ) {
2046
				$user_id = \Jetpack_Options::get_option( 'master_user' );
2047
				if ( ! $user_id ) {
2048
					return $suppress_errors ? false : new \WP_Error( 'empty_master_user_option' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'empty_master_user_option'.

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...
2049
				}
2050
			}
2051
			if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) {
2052
				return $suppress_errors ? false : new \WP_Error( 'no_token_for_user', sprintf( 'No token for user %d', $user_id ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_token_for_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...
2053
			}
2054
			$user_token_chunks = explode( '.', $user_tokens[ $user_id ] );
2055 View Code Duplication
			if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) {
2056
				return $suppress_errors ? false : new \WP_Error( 'token_malformed', sprintf( 'Token for user %d is malformed', $user_id ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'token_malformed'.

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

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

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

Loading history...
2057
			}
2058 View Code Duplication
			if ( $user_token_chunks[2] !== (string) $user_id ) {
2059
				return $suppress_errors ? false : new \WP_Error( 'user_id_mismatch', sprintf( 'Requesting user_id %d does not match token user_id %d', $user_id, $user_token_chunks[2] ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'user_id_mismatch'.

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

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

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

Loading history...
2060
			}
2061
			$possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}";
2062
		} else {
2063
			$stored_blog_token = \Jetpack_Options::get_option( 'blog_token' );
2064
			if ( $stored_blog_token ) {
2065
				$possible_normal_tokens[] = $stored_blog_token;
2066
			}
2067
2068
			$defined_tokens_string = Constants::get_constant( 'JETPACK_BLOG_TOKEN' );
2069
2070
			if ( $defined_tokens_string ) {
2071
				$defined_tokens = explode( ',', $defined_tokens_string );
2072
				foreach ( $defined_tokens as $defined_token ) {
2073
					if ( ';' === $defined_token[0] ) {
2074
						$possible_special_tokens[] = $defined_token;
2075
					} else {
2076
						$possible_normal_tokens[] = $defined_token;
2077
					}
2078
				}
2079
			}
2080
		}
2081
2082
		if ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
2083
			$possible_tokens = $possible_normal_tokens;
2084
		} else {
2085
			$possible_tokens = array_merge( $possible_special_tokens, $possible_normal_tokens );
2086
		}
2087
2088
		if ( ! $possible_tokens ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $possible_tokens of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2089
			return $suppress_errors ? false : new \WP_Error( 'no_possible_tokens' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_possible_tokens'.

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...
2090
		}
2091
2092
		$valid_token = false;
2093
2094
		if ( false === $token_key ) {
2095
			// Use first token.
2096
			$valid_token = $possible_tokens[0];
2097
		} elseif ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) {
2098
			// Use first normal token.
2099
			$valid_token = $possible_tokens[0]; // $possible_tokens only contains normal tokens because of earlier check.
2100
		} else {
2101
			// Use the token matching $token_key or false if none.
2102
			// Ensure we check the full key.
2103
			$token_check = rtrim( $token_key, '.' ) . '.';
2104
2105
			foreach ( $possible_tokens as $possible_token ) {
2106
				if ( hash_equals( substr( $possible_token, 0, strlen( $token_check ) ), $token_check ) ) {
2107
					$valid_token = $possible_token;
2108
					break;
2109
				}
2110
			}
2111
		}
2112
2113
		if ( ! $valid_token ) {
2114
			return $suppress_errors ? false : new \WP_Error( 'no_valid_token' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_valid_token'.

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...
2115
		}
2116
2117
		return (object) array(
2118
			'secret'           => $valid_token,
2119
			'external_user_id' => (int) $user_id,
2120
		);
2121
	}
2122
2123
	/**
2124
	 * In some setups, $HTTP_RAW_POST_DATA can be emptied during some IXR_Server paths
2125
	 * since it is passed by reference to various methods.
2126
	 * Capture it here so we can verify the signature later.
2127
	 *
2128
	 * @param array $methods an array of available XMLRPC methods.
2129
	 * @return array the same array, since this method doesn't add or remove anything.
2130
	 */
2131
	public function xmlrpc_methods( $methods ) {
2132
		$this->raw_post_data = $GLOBALS['HTTP_RAW_POST_DATA'];
2133
		return $methods;
2134
	}
2135
2136
	/**
2137
	 * Resets the raw post data parameter for testing purposes.
2138
	 */
2139
	public function reset_raw_post_data() {
2140
		$this->raw_post_data = null;
2141
	}
2142
2143
	/**
2144
	 * Registering an additional method.
2145
	 *
2146
	 * @param array $methods an array of available XMLRPC methods.
2147
	 * @return array the amended array in case the method is added.
2148
	 */
2149
	public function public_xmlrpc_methods( $methods ) {
2150
		if ( array_key_exists( 'wp.getOptions', $methods ) ) {
2151
			$methods['wp.getOptions'] = array( $this, 'jetpack_get_options' );
2152
		}
2153
		return $methods;
2154
	}
2155
2156
	/**
2157
	 * Handles a getOptions XMLRPC method call.
2158
	 *
2159
	 * @param array $args method call arguments.
2160
	 * @return an amended XMLRPC server options array.
2161
	 */
2162
	public function jetpack_get_options( $args ) {
2163
		global $wp_xmlrpc_server;
2164
2165
		$wp_xmlrpc_server->escape( $args );
2166
2167
		$username = $args[1];
2168
		$password = $args[2];
2169
2170
		$user = $wp_xmlrpc_server->login( $username, $password );
2171
		if ( ! $user ) {
2172
			return $wp_xmlrpc_server->error;
2173
		}
2174
2175
		$options   = array();
2176
		$user_data = $this->get_connected_user_data();
2177
		if ( is_array( $user_data ) ) {
2178
			$options['jetpack_user_id']         = array(
2179
				'desc'     => __( 'The WP.com user ID of the connected user', 'jetpack' ),
2180
				'readonly' => true,
2181
				'value'    => $user_data['ID'],
2182
			);
2183
			$options['jetpack_user_login']      = array(
2184
				'desc'     => __( 'The WP.com username of the connected user', 'jetpack' ),
2185
				'readonly' => true,
2186
				'value'    => $user_data['login'],
2187
			);
2188
			$options['jetpack_user_email']      = array(
2189
				'desc'     => __( 'The WP.com user email of the connected user', 'jetpack' ),
2190
				'readonly' => true,
2191
				'value'    => $user_data['email'],
2192
			);
2193
			$options['jetpack_user_site_count'] = array(
2194
				'desc'     => __( 'The number of sites of the connected WP.com user', 'jetpack' ),
2195
				'readonly' => true,
2196
				'value'    => $user_data['site_count'],
2197
			);
2198
		}
2199
		$wp_xmlrpc_server->blog_options = array_merge( $wp_xmlrpc_server->blog_options, $options );
2200
		$args                           = stripslashes_deep( $args );
2201
		return $wp_xmlrpc_server->wp_getOptions( $args );
2202
	}
2203
2204
	/**
2205
	 * Adds Jetpack-specific options to the output of the XMLRPC options method.
2206
	 *
2207
	 * @param array $options standard Core options.
2208
	 * @return array amended options.
2209
	 */
2210
	public function xmlrpc_options( $options ) {
2211
		$jetpack_client_id = false;
2212
		if ( $this->is_active() ) {
2213
			$jetpack_client_id = \Jetpack_Options::get_option( 'id' );
2214
		}
2215
		$options['jetpack_version'] = array(
2216
			'desc'     => __( 'Jetpack Plugin Version', 'jetpack' ),
2217
			'readonly' => true,
2218
			'value'    => Constants::get_constant( 'JETPACK__VERSION' ),
2219
		);
2220
2221
		$options['jetpack_client_id'] = array(
2222
			'desc'     => __( 'The Client ID/WP.com Blog ID of this site', 'jetpack' ),
2223
			'readonly' => true,
2224
			'value'    => $jetpack_client_id,
2225
		);
2226
		return $options;
2227
	}
2228
2229
	/**
2230
	 * Resets the saved authentication state in between testing requests.
2231
	 */
2232
	public function reset_saved_auth_state() {
2233
		$this->xmlrpc_verification = null;
2234
	}
2235
2236
	/**
2237
	 * Sign a user role with the master access token.
2238
	 * If not specified, will default to the current user.
2239
	 *
2240
	 * @access public
2241
	 *
2242
	 * @param string $role    User role.
2243
	 * @param int    $user_id ID of the user.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user_id not be integer|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...
2244
	 * @return string Signed user role.
2245
	 */
2246
	public function sign_role( $role, $user_id = null ) {
2247
		if ( empty( $user_id ) ) {
2248
			$user_id = (int) get_current_user_id();
2249
		}
2250
2251
		if ( ! $user_id ) {
2252
			return false;
2253
		}
2254
2255
		$token = $this->get_access_token();
2256
		if ( ! $token || is_wp_error( $token ) ) {
2257
			return false;
2258
		}
2259
2260
		return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret );
2261
	}
2262
2263
	/**
2264
	 * Set the plugin instance.
2265
	 *
2266
	 * @param Plugin $plugin_instance The plugin instance.
2267
	 *
2268
	 * @return $this
2269
	 */
2270
	public function set_plugin_instance( Plugin $plugin_instance ) {
2271
		$this->plugin = $plugin_instance;
2272
2273
		return $this;
2274
	}
2275
2276
	/**
2277
	 * Retrieve the plugin management object.
2278
	 *
2279
	 * @return Plugin
2280
	 */
2281
	public function get_plugin() {
2282
		return $this->plugin;
2283
	}
2284
2285
	/**
2286
	 * Get all connected plugins information.
2287
	 *
2288
	 * @return array|\WP_Error
2289
	 */
2290
	public function get_connected_plugins() {
2291
		return Plugin_Storage::get_all();
2292
	}
2293
2294
}
2295