Completed
Push — add/e2e-connection-purchase-fl... ( d02cce...68beb5 )
by Yaroslav
14:07 queued 04:35
created

Jetpack_Cxn_Tests::test__token_health()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 7
nop 0
dl 0
loc 36
rs 8.4106
c 0
b 0
f 0
1
<?php
2
/**
3
 * Collection of tests to run on the Jetpack connection locally.
4
 *
5
 * @package Jetpack
6
 */
7
8
use Automattic\Jetpack\Connection\Client;
9
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
10
use Automattic\Jetpack\Status;
11
use Automattic\Jetpack\Connection\Utils as Connection_Utils;
12
use Automattic\Jetpack\Sync\Modules;
13
use Automattic\Jetpack\Sync\Settings as Sync_Settings;
14
use Automattic\Jetpack\Sync\Health as Sync_Health;
15
use Automattic\Jetpack\Sync\Sender as Sync_Sender;
16
use Automattic\Jetpack\Redirect;
17
18
/**
19
 * Class Jetpack_Cxn_Tests contains all of the actual tests.
20
 */
21
class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base {
22
23
	/**
24
	 * Jetpack_Cxn_Tests constructor.
25
	 */
26
	public function __construct() {
27
		parent::__construct();
28
29
		$methods = get_class_methods( 'Jetpack_Cxn_Tests' );
30
31
		foreach ( $methods as $method ) {
32
			if ( false === strpos( $method, 'test__' ) ) {
33
				continue;
34
			}
35
			$this->add_test( array( $this, $method ), $method, 'direct' );
36
		}
37
38
		/**
39
		 * Fires after loading default Jetpack Connection tests.
40
		 *
41
		 * @since 7.1.0
42
		 * @since 8.3.0 Passes the Jetpack_Cxn_Tests instance.
43
		 */
44
		do_action( 'jetpack_connection_tests_loaded', $this );
45
46
		/**
47
		 * Determines if the WP.com testing suite should be included.
48
		 *
49
		 * @since 7.1.0
50
		 * @since 8.1.0 Default false.
51
		 *
52
		 * @param bool $run_test To run the WP.com testing suite. Default false.
53
		 */
54
		if ( apply_filters( 'jetpack_debugger_run_self_test', false ) ) {
55
			/**
56
			 * Intentionally added last as it checks for an existing failure state before attempting.
57
			 * Generally, any failed location condition would result in the WP.com check to fail too, so
58
			 * we will skip it to avoid confusing error messages.
59
			 *
60
			 * Note: This really should be an 'async' test.
61
			 */
62
			$this->add_test( array( $this, 'last__wpcom_self_test' ), 'test__wpcom_self_test', 'direct' );
63
		}
64
	}
65
66
	/**
67
	 * Helper function to look up the expected master user and return the local WP_User.
68
	 *
69
	 * @return WP_User Jetpack's expected master user.
70
	 */
71
	protected function helper_retrieve_local_master_user() {
72
		$master_user = Jetpack_Options::get_option( 'master_user' );
73
		return new WP_User( $master_user );
74
	}
75
76
	/**
77
	 * Is Jetpack even connected and supposed to be talking to WP.com?
78
	 */
79
	protected function helper_is_jetpack_connected() {
80
		return Jetpack::is_active() && ! ( new Status() )->is_offline_mode();
81
	}
82
83
	/**
84
	 * Retrieve the `blog_token` if it exists.
85
	 *
86
	 * @return object|false
87
	 */
88
	protected function helper_get_blog_token() {
89
		return Jetpack::connection()->get_access_token();
90
	}
91
92
	/**
93
	 * Returns a support url based on using a development version.
94
	 */
95
	protected function helper_get_support_url() {
96
		return Jetpack::is_development_version()
97
			? Redirect::get_url( 'jetpack-contact-support-beta-group' )
98
			: Redirect::get_url( 'jetpack-contact-support' );
99
	}
100
101
	/**
102
	 * Gets translated support text.
103
	 */
104
	protected function helper_get_support_text() {
105
		return __( 'Please contact Jetpack support.', 'jetpack' );
106
	}
107
108
	/**
109
	 * Gets translated text to enable outbound requests.
110
	 *
111
	 * @param string $protocol Either 'HTTP' or 'HTTPS'.
112
	 *
113
	 * @return string The translated text.
114
	 */
115
	protected function helper_enable_outbound_requests( $protocol ) {
116
		return sprintf(
117
			/* translators: %1$s - request protocol, either http or https */
118
			__(
119
				'Your server did not successfully connect to the Jetpack server using %1$s
120
				Please ask your hosting provider to confirm your server can make outbound requests to jetpack.com.',
121
				'jetpack'
122
			),
123
			$protocol
124
		);
125
	}
126
127
	/**
128
	 * Returns 30 for use with a filter.
129
	 *
130
	 * To allow time for WP.com to run upstream testing, this function exists to increase the http_request_timeout value
131
	 * to 30.
132
	 *
133
	 * @return int 30
134
	 */
135
	public static function increase_timeout() {
136
		return 30; // seconds.
137
	}
138
139
	/**
140
	 * The test verifies the blog token exists.
141
	 *
142
	 * @return array
143
	 */
144
	protected function test__blog_token_if_exists() {
145 View Code Duplication
		if ( ! $this->helper_is_jetpack_connected() ) {
146
			return self::skipped_test(
147
				array(
148
					'name'              => __FUNCTION__,
149
					'short_description' => __( 'Jetpack is not connected. No blog token to check.', 'jetpack' ),
150
				)
151
			);
152
		}
153
		$blog_token = $this->helper_get_blog_token();
154
155 View Code Duplication
		if ( $blog_token ) {
156
			$result = self::passing_test( array( 'name' => __FUNCTION__ ) );
157
		} else {
158
			$result = self::failing_test(
159
				array(
160
					'name'              => __FUNCTION__,
161
					'short_description' => __( 'Blog token is missing.', 'jetpack' ),
162
					'action'            => admin_url( 'admin.php?page=jetpack#/dashboard' ),
163
					'action_label'      => __( 'Disconnect your site from WordPress.com, and connect it again.', 'jetpack' ),
164
				)
165
			);
166
		}
167
168
		return $result;
169
	}
170
171
	/**
172
	 * Test if Jetpack is connected.
173
	 */
174
	protected function test__check_if_connected() {
175
		$name = __FUNCTION__;
176
177 View Code Duplication
		if ( ! $this->helper_get_blog_token() ) {
178
			return self::skipped_test(
179
				array(
180
					'name'              => $name,
181
					'short_description' => __( 'Blog token is missing.', 'jetpack' ),
182
				)
183
			);
184
		}
185
186
		if ( $this->helper_is_jetpack_connected() ) {
187
			$result = self::passing_test(
188
				array(
189
					'name'             => $name,
190
					'label'            => __( 'Your site is connected to Jetpack', 'jetpack' ),
191
					'long_description' => sprintf(
192
						'<p>%1$s</p>' .
193
						'<p><span class="dashicons pass"><span class="screen-reader-text">%2$s</span></span> %3$s</p>',
194
						__( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
195
						/* translators: Screen reader text indicating a test has passed */
196
						__( 'Passed', 'jetpack' ),
197
						__( 'Your site is connected to Jetpack.', 'jetpack' )
198
					),
199
				)
200
			);
201
		} elseif ( ( new Status() )->is_offline_mode() ) {
202
			$result = self::skipped_test(
203
				array(
204
					'name'              => $name,
205
					'short_description' => __( 'Jetpack is in Offline Mode:', 'jetpack' ) . ' ' . Jetpack::development_mode_trigger_text(),
206
				)
207
			);
208
		} else {
209
			$result = self::failing_test(
210
				array(
211
					'name'              => $name,
212
					'short_description' => __( 'Your site is not connected to Jetpack', 'jetpack' ),
213
					'action'            => admin_url( 'admin.php?page=jetpack#/dashboard' ),
214
					'action_label'      => __( 'Reconnect your site now', 'jetpack' ),
215
					'long_description'  => sprintf(
216
						'<p>%1$s</p>' .
217
						'<p><span class="dashicons fail"><span class="screen-reader-text">%2$s</span></span> %3$s<strong> %4$s</strong></p>',
218
						__( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
219
						/* translators: screen reader text indicating a test failed */
220
						__( 'Error', 'jetpack' ),
221
						__( 'Your site is not connected to Jetpack.', 'jetpack' ),
222
						__( 'We recommend reconnecting Jetpack.', 'jetpack' )
223
					),
224
				)
225
			);
226
		}
227
228
		return $result;
229
	}
230
231
	/**
232
	 * Test that the master user still exists on this site.
233
	 *
234
	 * @return array Test results.
235
	 */
236
	protected function test__master_user_exists_on_site() {
237
		$name = __FUNCTION__;
238 View Code Duplication
		if ( ! $this->helper_is_jetpack_connected() ) {
239
			return self::skipped_test(
240
				array(
241
					'name'              => $name,
242
					'short_description' => __( 'Jetpack is not connected. No master user to check.', 'jetpack' ),
243
				)
244
			);
245
		}
246
		$local_user = $this->helper_retrieve_local_master_user();
247
248
		if ( $local_user->exists() ) {
249
			$result = self::passing_test( array( 'name' => $name ) );
250
		} else {
251
			$result = self::failing_test(
252
				array(
253
					'name'              => $name,
254
					'short_description' => __( 'The user who setup the Jetpack connection no longer exists on this site.', 'jetpack' ),
255
					'action_label'      => __( 'Please disconnect and reconnect Jetpack.', 'jetpack' ),
256
					'action'            => Redirect::get_url( 'jetpack-support-reconnecting-reinstalling-jetpack' ),
257
				)
258
			);
259
		}
260
261
		return $result;
262
	}
263
264
	/**
265
	 * Test that the master user has the manage options capability (e.g. is an admin).
266
	 *
267
	 * Generic calls from WP.com execute on Jetpack as the master user. If it isn't an admin, random things will fail.
268
	 *
269
	 * @return array Test results.
270
	 */
271
	protected function test__master_user_can_manage_options() {
272
		$name = __FUNCTION__;
273 View Code Duplication
		if ( ! $this->helper_is_jetpack_connected() ) {
274
			return self::skipped_test(
275
				array(
276
					'name'              => $name,
277
					'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
278
				)
279
			);
280
		}
281
		$master_user = $this->helper_retrieve_local_master_user();
282
283
		if ( user_can( $master_user, 'manage_options' ) ) {
284
			$result = self::passing_test( array( 'name' => $name ) );
285
		} else {
286
			$result = self::failing_test(
287
				array(
288
					'name'              => $name,
289
					/* translators: a WordPress username */
290
					'short_description' => sprintf( __( 'The user (%s) who setup the Jetpack connection is not an administrator.', 'jetpack' ), $master_user->user_login ),
291
					'action_label'      => __( 'Either upgrade the user or disconnect and reconnect Jetpack.', 'jetpack' ),
292
					'action'            => Redirect::get_url( 'jetpack-support-reconnecting-reinstalling-jetpack' ),
293
				)
294
			);
295
		}
296
297
		return $result;
298
	}
299
300
	/**
301
	 * Test that the PHP's XML library is installed.
302
	 *
303
	 * While it should be installed by default, increasingly in PHP 7, some OSes require an additional php-xml package.
304
	 *
305
	 * @return array Test results.
306
	 */
307
	protected function test__xml_parser_available() {
308
		$name = __FUNCTION__;
309
		if ( function_exists( 'xml_parser_create' ) ) {
310
			$result = self::passing_test( array( 'name' => $name ) );
311 View Code Duplication
		} else {
312
			$result = self::failing_test(
313
				array(
314
					'name'              => $name,
315
					'label'             => __( 'PHP XML manipulation libraries are not available.', 'jetpack' ),
316
					'short_description' => __( 'Please ask your hosting provider to refer to our server requirements and enable PHP\'s XML module.', 'jetpack' ),
317
					'action_label'      => __( 'View our server requirements', 'jetpack' ),
318
					'action'            => Redirect::get_url( 'jetpack-support-server-requirements' ),
319
				)
320
			);
321
		}
322
		return $result;
323
	}
324
325
	/**
326
	 * Test that the server is able to send an outbound http communication.
327
	 *
328
	 * @return array Test results.
329
	 */
330 View Code Duplication
	protected function test__outbound_http() {
331
		$name    = __FUNCTION__;
332
		$request = wp_remote_get( preg_replace( '/^https:/', 'http:', JETPACK__API_BASE ) . 'test/1/' );
333
		$code    = wp_remote_retrieve_response_code( $request );
334
335
		if ( 200 === intval( $code ) ) {
336
			$result = self::passing_test( array( 'name' => $name ) );
337
		} else {
338
			$result = self::failing_test(
339
				array(
340
					'name'              => $name,
341
					'short_description' => $this->helper_enable_outbound_requests( 'HTTP' ),
342
				)
343
			);
344
		}
345
346
		return $result;
347
	}
348
349
	/**
350
	 * Test that the server is able to send an outbound https communication.
351
	 *
352
	 * @return array Test results.
353
	 */
354 View Code Duplication
	protected function test__outbound_https() {
355
		$name    = __FUNCTION__;
356
		$request = wp_remote_get( preg_replace( '/^http:/', 'https:', JETPACK__API_BASE ) . 'test/1/' );
357
		$code    = wp_remote_retrieve_response_code( $request );
358
359
		if ( 200 === intval( $code ) ) {
360
			$result = self::passing_test( array( 'name' => $name ) );
361
		} else {
362
			$result = self::failing_test(
363
				array(
364
					'name'              => $name,
365
					'short_description' => $this->helper_enable_outbound_requests( 'HTTPS' ),
366
				)
367
			);
368
		}
369
370
		return $result;
371
	}
372
373
	/**
374
	 * Check for an IDC.
375
	 *
376
	 * @return array Test results.
377
	 */
378
	protected function test__identity_crisis() {
379
		$name = __FUNCTION__;
380 View Code Duplication
		if ( ! $this->helper_is_jetpack_connected() ) {
381
			return self::skipped_test(
382
				array(
383
					'name'              => $name,
384
					'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
385
				)
386
			);
387
		}
388
		$identity_crisis = Jetpack::check_identity_crisis();
389
390
		if ( ! $identity_crisis ) {
391
			$result = self::passing_test( array( 'name' => $name ) );
392
		} else {
393
			$result = self::failing_test(
394
				array(
395
					'name'              => $name,
396
					'short_description' => sprintf(
397
						/* translators: Two URLs. The first is the locally-recorded value, the second is the value as recorded on WP.com. */
398
						__( 'Your url is set as `%1$s`, but your WordPress.com connection lists it as `%2$s`!', 'jetpack' ),
399
						$identity_crisis['home'],
400
						$identity_crisis['wpcom_home']
401
					),
402
					'action_label'      => $this->helper_get_support_text(),
403
					'action'            => $this->helper_get_support_url(),
404
				)
405
			);
406
		}
407
		return $result;
408
	}
409
410
	/**
411
	 * Tests blog and current user's token against wp.com's check-token-health endpoint.
412
	 *
413
	 * @since 9.0.0
414
	 *
415
	 * @return array Test results.
416
	 */
417
	protected function test__token_health() {
418
		$name = __FUNCTION__;
419
420
		$m                = new Connection_Manager();
421
		$validated_tokens = $m->validate_tokens( get_current_user_id() ? get_current_user_id() : $m->get_connection_owner_id() );
422
423
		if ( ! is_array( $validated_tokens ) || count( array_diff_key( array_flip( array( 'blog_token', 'user_token' ) ), $validated_tokens ) ) ) {
424
			return self::skipped_test(
425
				array(
426
					'name'              => $name,
427
					'short_description' => __( 'Token health check failed to validate tokens.', 'jetpack' ),
428
				)
429
			);
430
		}
431
432
		$invalid_tokens_exist = false;
433
		foreach ( $validated_tokens as $validated_token ) {
434
			if ( ! $validated_token['is_healthy'] ) {
435
				$invalid_tokens_exist = true;
436
				break;
437
			}
438
		}
439
440
		if ( false === $invalid_tokens_exist ) {
441
			return self::passing_test( array( 'name' => $name ) );
442
		}
443
444
		return self::failing_test(
445
			array(
446
				'name'              => $name,
447
				'short_description' => __( 'Connection test failed: Invalid Jetpack tokens', 'jetpack' ),
448
				'action_label'      => __( 'Please disconnect and reconnect Jetpack.', 'jetpack' ),
449
				'action'            => Redirect::get_url( 'jetpack-support-reconnecting-reinstalling-jetpack' ),
450
			)
451
		);
452
	}
453
454
	/**
455
	 * Tests connection status against wp.com's test-connection endpoint.
456
	 *
457
	 * @todo: Compare with the wpcom_self_test. We only need one of these.
458
	 *
459
	 * @return array Test results.
460
	 */
461
	protected function test__wpcom_connection_test() {
462
		$name = __FUNCTION__;
463
464
		$status = new Status();
465 View Code Duplication
		if ( ! Jetpack::is_active() || $status->is_offline_mode() || $status->is_staging_site() || ! $this->pass ) {
466
			return self::skipped_test( array( 'name' => $name ) );
467
		}
468
469
		add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
470
		$response = Client::wpcom_json_api_request_as_blog(
471
			sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
472
			Client::WPCOM_JSON_API_VERSION
473
		);
474
		remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
475
476 View Code Duplication
		if ( is_wp_error( $response ) ) {
477
			return self::failing_test(
478
				array(
479
					'name'              => $name,
480
					/* translators: %1$s is the error code, %2$s is the error message */
481
					'short_description' => sprintf( __( 'Connection test failed (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() ),
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

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

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

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

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

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

Loading history...
482
					'action_label'      => $this->helper_get_support_text(),
483
					'action'            => $this->helper_get_support_url(),
484
				)
485
			);
486
		}
487
488
		$body = wp_remote_retrieve_body( $response );
489 View Code Duplication
		if ( ! $body ) {
490
			return self::failing_test(
491
				array(
492
					'name'              => $name,
493
					'short_description' => __( 'Connection test failed (empty response body)', 'jetpack' ) . wp_remote_retrieve_response_code( $response ),
494
					'action_label'      => $this->helper_get_support_text(),
495
					'action'            => $this->helper_get_support_url(),
496
				)
497
			);
498
		}
499
500
		if ( 404 === wp_remote_retrieve_response_code( $response ) ) {
501
			return self::skipped_test(
502
				array(
503
					'name'              => $name,
504
					'short_description' => __( 'The WordPress.com API returned a 404 error.', 'jetpack' ),
505
				)
506
			);
507
		}
508
509
		$result       = json_decode( $body );
510
		$is_connected = ! empty( $result->connected );
511
		$message      = $result->message . ': ' . wp_remote_retrieve_response_code( $response );
512
513
		if ( $is_connected ) {
514
			return self::passing_test( array( 'name' => $name ) );
515
		} else {
516
			return self::failing_test(
517
				array(
518
					'name'              => $name,
519
					'short_description' => $message,
520
					'action_label'      => $this->helper_get_support_text(),
521
					'action'            => $this->helper_get_support_url(),
522
				)
523
			);
524
		}
525
	}
526
527
	/**
528
	 * Tests the port number to ensure it is an expected value.
529
	 *
530
	 * We expect that sites on be on one of:
531
	 * port 80,
532
	 * port 443 (https sites only),
533
	 * the value of JETPACK_SIGNATURE__HTTP_PORT,
534
	 * unless the site is intentionally on a different port (e.g. example.com:8080 is the site's URL).
535
	 *
536
	 * If the value isn't one of those and the site's URL doesn't include a port, then the signature verification will fail.
537
	 *
538
	 * This happens most commonly on sites with reverse proxies, so the edge (e.g. Varnish) is running on 80/443, but nginx
539
	 * or Apache is responding internally on a different port (e.g. 81).
540
	 *
541
	 * @return array Test results
542
	 */
543
	protected function test__server_port_value() {
544
		$name = __FUNCTION__;
545
		if ( ! isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) && ! isset( $_SERVER['SERVER_PORT'] ) ) {
546
			return self::skipped_test(
547
				array(
548
					'name'              => $name,
549
					'short_description' => __( 'The server port values are not defined. This is most common when running PHP via a CLI.', 'jetpack' ),
550
				)
551
			);
552
		}
553
		$site_port   = wp_parse_url( home_url(), PHP_URL_PORT );
0 ignored issues
show
Unused Code introduced by
The call to wp_parse_url() has too many arguments starting with PHP_URL_PORT.

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...
554
		$server_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? (int) $_SERVER['HTTP_X_FORWARDED_PORT'] : (int) $_SERVER['SERVER_PORT'];
555
		$http_ports  = array( 80 );
556
		$https_ports = array( 80, 443 );
557
558
		if ( defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ) {
559
			$http_ports[] = JETPACK_SIGNATURE__HTTP_PORT;
560
		}
561
562
		if ( defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ) {
563
			$https_ports[] = JETPACK_SIGNATURE__HTTPS_PORT;
564
		}
565
566
		if ( $site_port ) {
567
			return self::skipped_test( array( 'name' => $name ) ); // Not currently testing for this situation.
568
		}
569
570
		if ( is_ssl() && in_array( $server_port, $https_ports, true ) ) {
571
			return self::passing_test( array( 'name' => $name ) );
572
		} elseif ( in_array( $server_port, $http_ports, true ) ) {
573
			return self::passing_test( array( 'name' => $name ) );
574
		} else {
575
			if ( is_ssl() ) {
576
				$needed_constant = 'JETPACK_SIGNATURE__HTTPS_PORT';
577
			} else {
578
				$needed_constant = 'JETPACK_SIGNATURE__HTTP_PORT';
579
			}
580
			return self::failing_test(
581
				array(
582
					'name'              => $name,
583
					'short_description' => sprintf(
584
						/* translators: %1$s - a PHP code snippet */
585
						__(
586
							'The server port value is unexpected.
587
						Try adding the following to your wp-config.php file: %1$s',
588
							'jetpack'
589
						),
590
						"define( '$needed_constant', $server_port )"
591
					),
592
				)
593
			);
594
		}
595
	}
596
597
	/**
598
	 * Full Sync Health Test.
599
	 *
600
	 * Sync Disabled: Results in a skipped test
601
	 * Not In Progress : Results in a skipped test
602
	 * In Progress: Results in skipped test w/ status in CLI
603
	 */
604
	protected function test__full_sync_health() {
605
606
		$name = __FUNCTION__;
607
608
		if ( ! $this->helper_is_jetpack_connected() ) {
609
			// If the site is not connected, there is no point in testing Sync health.
610
			return self::skipped_test(
611
				array(
612
					'name'                => $name,
613
					'show_in_site_health' => false,
614
				)
615
			);
616
		}
617
618
		// Sync is enabled.
619
		if ( Sync_Settings::is_sync_enabled() ) {
620
621
			// Get Full Sync Progress.
622
			$full_sync_module = Modules::get_module( 'full-sync' );
623
			$progress_percent = $full_sync_module ? $full_sync_module->get_sync_progress_percentage() : null;
624
625
			// Full Sync in Progress.
626
			if ( $progress_percent ) {
627
628
				return self::informational_test(
629
					array(
630
						'name'              => $name,
631
						'label'             => __( 'Jetpack is performing a full sync of your site', 'jetpack' ),
632
						'severity'          => 'recommended',
633
						/* translators: placeholder is a percentage number. */
634
						'short_description' => sprintf( __( 'Jetpack is performing a full sync of your site. Current Progress: %1$d %%', 'jetpack' ), $progress_percent ),
635
						'long_description'  => sprintf(
636
							'<p>%1$s</p><p><span class="dashicons dashicons-update"><span class="screen-reader-text">%2$s</span></span> %3$s</p><div class="jetpack-sync-progress-ui"><div class="jetpack-sync-progress-label"></div><div class="jetpack-sync-progress-bar"></div></div>',
637
							__( 'The information synced by Jetpack ensures that Jetpack Search, Related Posts and other features are aligned with your site’s current content.', 'jetpack' ), /* translators: screen reader text indicating data is updating. */
638
							__( 'Updating', 'jetpack' ),
639
							__( 'Jetpack is currently performing a full sync of your site data.', 'jetpack' )
640
						),
641
					)
642
				);
643
644
			} else {
645
646
				// no Full Sync in Progress.
647
				return self::skipped_test(
648
					array(
649
						'name'                => $name,
650
						'show_in_site_health' => false,
651
					)
652
				);
653
654
			}
655
		} else {
656
657
			// If sync is not enabled no Full Sync can occur.
658
			return self::skipped_test(
659
				array(
660
					'name'                => $name,
661
					'show_in_site_health' => false,
662
				)
663
			);
664
665
		}
666
667
	}
668
669
	/**
670
	 * Sync Health Tests.
671
	 *
672
	 * Disabled: Results in a failing test (recommended)
673
	 * Delayed: Results in failing test (recommended)
674
	 * Error: Results in failing test (critical)
675
	 */
676
	protected function test__sync_health() {
677
678
		$name = __FUNCTION__;
679
680
		if ( ! $this->helper_is_jetpack_connected() ) {
681
			// If the site is not connected, there is no point in testing Sync health.
682
			return self::skipped_test(
683
				array(
684
					'name'                => $name,
685
					'show_in_site_health' => false,
686
				)
687
			);
688
		}
689
690
		// Sync is enabled.
691
		if ( Sync_Settings::is_sync_enabled() ) {
692
693
			if ( Sync_Health::get_status() === Sync_Health::STATUS_OUT_OF_SYNC ) {
694
				/*
695
				 * Sync has experienced Data Loss.
696
				 */
697
				$description  = '<p>';
698
				$description .= esc_html__( 'The information synced by Jetpack ensures that Jetpack Search, Related Posts and other features are aligned with your site’s current content.', 'jetpack' );
699
				$description .= '</p>';
700
				$description .= '<p>';
701
				$description .= sprintf(
702
					'<span class="dashicons fail"><span class="screen-reader-text">%1$s</span></span> ',
703
					esc_html__( 'Error', 'jetpack' )
704
				);
705
				$description .= wp_kses(
706
					__( 'Jetpack has detected an error while syncing your site. <strong>We recommend <a id="full_sync_request_link" href="#">a full sync</a> to align Jetpack with your site data.</strong>', 'jetpack' ),
707
					array(
708
						'a'      => array(
709
							'id'   => array(),
710
							'href' => array(),
711
						),
712
						'strong' => array(),
713
					)
714
				);
715
				$description .= '</p>';
716
717
				return self::failing_test(
718
					array(
719
						'name'              => $name,
720
						'label'             => __( 'Jetpack has detected an error syncing your site.', 'jetpack' ),
721
						'severity'          => 'critical',
722
						'action'            => Redirect::get_url( 'jetpack-contact-support' ),
723
						'action_label'      => __( 'Contact Jetpack Support', 'jetpack' ),
724
						'short_description' => __( 'Jetpack has detected an error while syncing your site. We recommend a full sync to align Jetpack with your site data.', 'jetpack' ),
725
						'long_description'  => $description,
726
					)
727
				);
728
729
			} else {
730
				// Get the Sync Queue.
731
				$sender     = Sync_Sender::get_instance();
732
				$sync_queue = $sender->get_sync_queue();
733
734
				// lag exceeds 5 minutes.
735
				if ( $sync_queue->lag() > 5 * MINUTE_IN_SECONDS ) {
736
737
					$description  = '<p>';
738
					$description .= esc_html__( 'The information synced by Jetpack ensures that Jetpack Search, Related Posts and other features are aligned with your site’s current content.', 'jetpack' );
739
					$description .= '</p>';
740
					$description .= '<p>';
741
					$description .= sprintf(
742
						'<span class="dashicons dashicons-clock" style="color: orange;"><span class="screen-reader-text">%1$s</span></span> ',
743
						/* translators: name, used to describe a clock icon. */
744
						esc_html__( 'Clock', 'jetpack' )
745
					);
746
					$description .= wp_kses(
747
						sprintf(
748
							/* translators: placeholder is a number of minutes. */
749
							_n(
750
								'Jetpack has identified a delay while syncing individual content updates. Certain features might be slower than usual, but this is only temporary while sync catches up with recent changes to your site. <strong>We’re seeing a current delay of %1$d minute.</strong>',
751
								'Jetpack has identified a delay while syncing individual content updates. Certain features might be slower than usual, but this is only temporary while sync catches up with recent changes to your site. <strong>We’re seeing a current delay of %1$d minutes.</strong>',
752
								intval( $sync_queue->lag() / MINUTE_IN_SECONDS ),
753
								'jetpack'
754
							),
755
							number_format_i18n( $sync_queue->lag() / MINUTE_IN_SECONDS )
756
						),
757
						array( 'strong' => array() )
758
					);
759
					$description .= '</p>';
760
761
					return self::informational_test(
762
						array(
763
							'name'              => $name,
764
							'label'             => __( 'Jetpack is experiencing a delay syncing your site.', 'jetpack' ),
765
							'severity'          => 'recommended',
766
							'action'            => null,
767
							'action_label'      => null,
768
							'short_description' => __( 'Jetpack is experiencing a delay syncing your site.', 'jetpack' ),
769
							'long_description'  => $description,
770
						)
771
					);
772
773
				} else {
774
775
					// Sync is Healthy.
776
					return self::passing_test( array( 'name' => $name ) );
777
778
				}
779
			}
780
		} else {
781
			/*
782
			 * Sync is disabled.
783
			 */
784
785
			$description  = '<p>';
786
			$description .= esc_html__( 'The information synced by Jetpack ensures that Jetpack Search, Related Posts and other features are aligned with your site’s current content.', 'jetpack' );
787
			$description .= '</p>';
788
			$description .= '<p>';
789
			$description .= __( 'Developers may enable / disable syncing using the Sync Settings API.', 'jetpack' );
790
			$description .= '</p>';
791
			$description .= '<p>';
792
			$description .= sprintf(
793
				'<span class="dashicons fail"><span class="screen-reader-text">%1$s</span></span> ',
794
				esc_html__( 'Error', 'jetpack' )
795
			);
796
			$description .= wp_kses(
797
				__( 'Jetpack Sync has been disabled on your site. Without it, certain Jetpack features will not work. <strong>We recommend enabling Sync.</strong>', 'jetpack' ),
798
				array( 'strong' => array() )
799
			);
800
			$description .= '</p>';
801
802
			return self::failing_test(
803
				array(
804
					'name'              => $name,
805
					'label'             => __( 'Jetpack Sync has been disabled on your site.', 'jetpack' ),
806
					'severity'          => 'recommended',
807
					'action'            => 'https://github.com/Automattic/jetpack/blob/master/packages/sync/src/class-settings.php',
808
					'action_label'      => __( 'See Github for more on Sync Settings', 'jetpack' ),
809
					'short_description' => __( 'Jetpack Sync has been disabled on your site.', 'jetpack' ),
810
					'long_description'  => $description,
811
				)
812
			);
813
814
		}
815
816
	}
817
818
	/**
819
	 * Calls to WP.com to run the connection diagnostic testing suite.
820
	 *
821
	 * Intentionally added last as it will be skipped if any local failed conditions exist.
822
	 *
823
	 * @since 7.1.0
824
	 * @since 7.9.0 Timeout waiting for a WP.com response no longer fails the test. Test is marked skipped instead.
825
	 *
826
	 * @return array Test results.
827
	 */
828
	protected function last__wpcom_self_test() {
829
		$name = 'test__wpcom_self_test';
830
831
		$status = new Status();
832 View Code Duplication
		if ( ! Jetpack::is_active() || $status->is_offline_mode() || $status->is_staging_site() || ! $this->pass ) {
833
			return self::skipped_test( array( 'name' => $name ) );
834
		}
835
836
		$self_xml_rpc_url = site_url( 'xmlrpc.php' );
837
838
		$testsite_url = Connection_Utils::fix_url_for_bad_hosts( JETPACK__API_BASE . 'testsite/1/?url=' );
839
840
		// Using PHP_INT_MAX - 1 so that there is still a way to override this if needed and since it only impacts this one call.
841
		add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ), PHP_INT_MAX - 1 );
842
843
		$response = wp_remote_get( $testsite_url . $self_xml_rpc_url );
844
845
		remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ), PHP_INT_MAX - 1 );
846
847
		if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
848
			return self::passing_test( array( 'name' => $name ) );
849
		} elseif ( is_wp_error( $response ) && false !== strpos( $response->get_error_message(), 'cURL error 28' ) ) { // Timeout.
850
			return self::skipped_test(
851
				array(
852
					'name'              => $name,
853
					'short_description' => __( 'The test timed out which may sometimes indicate a failure or may be a false failure.', 'jetpack' ),
854
				)
855
			);
856
		} else {
857
			return self::failing_test(
858
				array(
859
					'name'              => $name,
860
					'short_description' => sprintf(
861
						/* translators: %1$s - A debugging url */
862
						__( 'Jetpack.com detected an error on the WP.com Self Test. Visit the Jetpack Debug page for more info: %1$s, or contact support.', 'jetpack' ),
863
						Redirect::get_url( 'jetpack-support-debug', array( 'query' => 'url=' . rawurlencode( site_url() ) ) )
864
					),
865
					'action_label'      => $this->helper_get_support_text(),
866
					'action'            => $this->helper_get_support_url(),
867
				)
868
			);
869
		}
870
	}
871
}
872