Completed
Push — try/jetpack-stories-block-mobi... ( 2fea66 )
by
unknown
126:35 queued 116:47
created

Jetpack_Cxn_Tests::helper_is_jetpack_connected()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 3
rs 10
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\Redirect;
11
use Automattic\Jetpack\Status;
12
use Automattic\Jetpack\Sync\Health as Sync_Health;
13
use Automattic\Jetpack\Sync\Modules;
14
use Automattic\Jetpack\Sync\Sender as Sync_Sender;
15
use Automattic\Jetpack\Sync\Settings as Sync_Settings;
16
17
/**
18
 * Class Jetpack_Cxn_Tests contains all of the actual tests.
19
 */
20
class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base {
21
22
	/**
23
	 * Jetpack_Cxn_Tests constructor.
24
	 */
25
	public function __construct() {
26
		parent::__construct();
27
28
		$methods = get_class_methods( 'Jetpack_Cxn_Tests' );
29
30
		foreach ( $methods as $method ) {
31
			if ( false === strpos( $method, 'test__' ) ) {
32
				continue;
33
			}
34
			$this->add_test( array( $this, $method ), $method, 'direct' );
35
		}
36
37
		/**
38
		 * Fires after loading default Jetpack Connection tests.
39
		 *
40
		 * @since 7.1.0
41
		 * @since 8.3.0 Passes the Jetpack_Cxn_Tests instance.
42
		 */
43
		do_action( 'jetpack_connection_tests_loaded', $this );
44
45
		/**
46
		 * Determines if the WP.com testing suite should be included.
47
		 *
48
		 * @since 7.1.0
49
		 * @since 8.1.0 Default false.
50
		 *
51
		 * @param bool $run_test To run the WP.com testing suite. Default false.
52
		 */
53
		if ( apply_filters( 'jetpack_debugger_run_self_test', false ) ) {
54
			/**
55
			 * Intentionally added last as it checks for an existing failure state before attempting.
56
			 * Generally, any failed location condition would result in the WP.com check to fail too, so
57
			 * we will skip it to avoid confusing error messages.
58
			 *
59
			 * Note: This really should be an 'async' test.
60
			 */
61
			$this->add_test( array( $this, 'last__wpcom_self_test' ), 'test__wpcom_self_test', 'direct' );
62
		}
63
	}
64
65
	/**
66
	 * Helper function to look up the expected master user and return the local WP_User.
67
	 *
68
	 * @return WP_User Jetpack's expected master user.
69
	 */
70
	protected function helper_retrieve_local_master_user() {
71
		$master_user = Jetpack_Options::get_option( 'master_user' );
72
		return new WP_User( $master_user );
73
	}
74
75
	/**
76
	 * Is Jetpack even connected and supposed to be talking to WP.com?
77
	 */
78
	protected function helper_is_jetpack_connected() {
79
		return Jetpack::is_active() && ! ( new Status() )->is_offline_mode();
80
	}
81
82
	/**
83
	 * Retrieve the `blog_token` if it exists.
84
	 *
85
	 * @return object|false
86
	 */
87
	protected function helper_get_blog_token() {
88
		return Jetpack::connection()->get_access_token();
89
	}
90
91
	/**
92
	 * Returns a support url based on using a development version.
93
	 */
94
	protected function helper_get_support_url() {
95
		return Jetpack::is_development_version()
96
			? Redirect::get_url( 'jetpack-contact-support-beta-group' )
97
			: Redirect::get_url( 'jetpack-contact-support' );
98
	}
99
100
	/**
101
	 * Returns the url to reconnect Jetpack.
102
	 *
103
	 * @return string The reconnect url.
104
	 */
105
	protected static function helper_get_reconnect_url() {
106
		return admin_url( 'admin.php?page=jetpack#/reconnect' );
107
	}
108
109
	/**
110
	 * Gets translated support text.
111
	 */
112
	protected function helper_get_support_text() {
113
		return __( 'Please contact Jetpack support.', 'jetpack' );
114
	}
115
116
	/**
117
	 * Returns the translated text to reconnect Jetpack.
118
	 *
119
	 * @return string The translated reconnect text.
120
	 */
121
	protected static function helper_get_reconnect_text() {
122
		return __( 'Reconnect Jetpack now', 'jetpack' );
123
	}
124
125
	/**
126
	 * Returns the translated text for failing tests due to timeouts.
127
	 *
128
	 * @return string The translated timeout text.
129
	 */
130
	protected static function helper_get_timeout_text() {
131
		return __( 'The test timed out which may sometimes indicate a failure or may be a false failure. Please relaunch tests.', 'jetpack' );
132
	}
133
134
	/**
135
	 * Gets translated reconnect long description.
136
	 *
137
	 * @param string $connection_error The connection specific error.
138
	 * @param string $recommendation The recommendation for resolving the connection error.
139
	 *
140
	 * @return string The translated long description for reconnection recommendations.
141
	 */
142
	protected static function helper_get_reconnect_long_description( $connection_error, $recommendation ) {
143
144
		return sprintf(
145
			'<p>%1$s</p>' .
146
			'<p><span class="dashicons fail"><span class="screen-reader-text">%2$s</span></span> %3$s</p><p><strong>%4$s</strong></p>',
147
			__( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
148
			/* translators: screen reader text indicating a test failed */
149
			__( 'Error', 'jetpack' ),
150
			$connection_error,
151
			$recommendation
152
		);
153
	}
154
155
	/**
156
	 * Helper function to return consistent responses for a connection failing test.
157
	 *
158
	 * @param string $name The raw method name that runs the test. Default unnamed_test.
159
	 * @param string $connection_error The connection specific error. Default 'Your site is not connected to Jetpack.'.
160
	 * @param string $recommendation The recommendation for resolving the connection error. Default 'We recommend reconnecting Jetpack.'.
161
	 *
162
	 * @return array Test results.
163
	 */
164
	public static function connection_failing_test( $name, $connection_error = '', $recommendation = '' ) {
165
		$connection_error = empty( $connection_error ) ? __( 'Your site is not connected to Jetpack.', 'jetpack' ) : $connection_error;
166
		$recommendation   = empty( $recommendation ) ? __( 'We recommend reconnecting Jetpack.', 'jetpack' ) : $recommendation;
167
168
		$args = array(
169
			'name'              => $name,
170
			'short_description' => $connection_error,
171
			'action'            => self::helper_get_reconnect_url(),
172
			'action_label'      => self::helper_get_reconnect_text(),
173
			'long_description'  => self::helper_get_reconnect_long_description( $connection_error, $recommendation ),
174
		);
175
176
		return self::failing_test( $args );
177
	}
178
179
	/**
180
	 * Gets translated text to enable outbound requests.
181
	 *
182
	 * @param string $protocol Either 'HTTP' or 'HTTPS'.
183
	 *
184
	 * @return string The translated text.
185
	 */
186
	protected function helper_enable_outbound_requests( $protocol ) {
187
		return sprintf(
188
			/* translators: %1$s - request protocol, either http or https */
189
			__(
190
				'Your server did not successfully connect to the Jetpack server using %1$s
191
				Please ask your hosting provider to confirm your server can make outbound requests to jetpack.com.',
192
				'jetpack'
193
			),
194
			$protocol
195
		);
196
	}
197
198
	/**
199
	 * Returns 30 for use with a filter.
200
	 *
201
	 * To allow time for WP.com to run upstream testing, this function exists to increase the http_request_timeout value
202
	 * to 30.
203
	 *
204
	 * @return int 30
205
	 */
206
	public static function increase_timeout() {
207
		return 30; // seconds.
208
	}
209
210
	/**
211
	 * The test verifies the blog token exists.
212
	 *
213
	 * @return array
214
	 */
215 View Code Duplication
	protected function test__blog_token_if_exists() {
216
		$name = __FUNCTION__;
217
218
		if ( ! $this->helper_is_jetpack_connected() ) {
219
			return self::skipped_test(
220
				array(
221
					'name'              => $name,
222
					'short_description' => __( 'Jetpack is not connected. No blog token to check.', 'jetpack' ),
223
				)
224
			);
225
		}
226
		$blog_token = $this->helper_get_blog_token();
227
228
		if ( $blog_token ) {
229
			$result = self::passing_test( array( 'name' => $name ) );
230
		} else {
231
			$connection_error = __( 'Blog token is missing.', 'jetpack' );
232
233
			$result = self::connection_failing_test( $name, $connection_error );
234
		}
235
236
		return $result;
237
	}
238
239
	/**
240
	 * Test if Jetpack is connected.
241
	 */
242
	protected function test__check_if_connected() {
243
		$name = __FUNCTION__;
244
245
		if ( ! $this->helper_get_blog_token() ) {
246
			return self::skipped_test(
247
				array(
248
					'name'              => $name,
249
					'short_description' => __( 'Blog token is missing.', 'jetpack' ),
250
				)
251
			);
252
		}
253
254
		if ( $this->helper_is_jetpack_connected() ) {
255
			$result = self::passing_test(
256
				array(
257
					'name'             => $name,
258
					'label'            => __( 'Your site is connected to Jetpack', 'jetpack' ),
259
					'long_description' => sprintf(
260
						'<p>%1$s</p>' .
261
						'<p><span class="dashicons pass"><span class="screen-reader-text">%2$s</span></span> %3$s</p>',
262
						__( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
263
						/* translators: Screen reader text indicating a test has passed */
264
						__( 'Passed', 'jetpack' ),
265
						__( 'Your site is connected to Jetpack.', 'jetpack' )
266
					),
267
				)
268
			);
269
		} elseif ( ( new Status() )->is_offline_mode() ) {
270
			$result = self::skipped_test(
271
				array(
272
					'name'              => $name,
273
					'short_description' => __( 'Jetpack is in Offline Mode:', 'jetpack' ) . ' ' . Jetpack::development_mode_trigger_text(),
274
				)
275
			);
276
		} else {
277
			$connection_error = __( 'Your site is not connected to Jetpack', 'jetpack' );
278
279
			$result = self::connection_failing_test( $name, $connection_error );
280
		}
281
282
		return $result;
283
	}
284
285
	/**
286
	 * Test that the master user still exists on this site.
287
	 *
288
	 * @return array Test results.
289
	 */
290 View Code Duplication
	protected function test__master_user_exists_on_site() {
291
		$name = __FUNCTION__;
292
		if ( ! $this->helper_is_jetpack_connected() ) {
293
			return self::skipped_test(
294
				array(
295
					'name'              => $name,
296
					'short_description' => __( 'Jetpack is not connected. No master user to check.', 'jetpack' ),
297
				)
298
			);
299
		}
300
		$local_user = $this->helper_retrieve_local_master_user();
301
302
		if ( $local_user->exists() ) {
303
			$result = self::passing_test( array( 'name' => $name ) );
304
		} else {
305
			$connection_error = __( 'The user who setup the Jetpack connection no longer exists on this site.', 'jetpack' );
306
307
			$result = self::connection_failing_test( $name, $connection_error );
308
		}
309
310
		return $result;
311
	}
312
313
	/**
314
	 * Test that the master user has the manage options capability (e.g. is an admin).
315
	 *
316
	 * Generic calls from WP.com execute on Jetpack as the master user. If it isn't an admin, random things will fail.
317
	 *
318
	 * @return array Test results.
319
	 */
320
	protected function test__master_user_can_manage_options() {
321
		$name = __FUNCTION__;
322
		if ( ! $this->helper_is_jetpack_connected() ) {
323
			return self::skipped_test(
324
				array(
325
					'name'              => $name,
326
					'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
327
				)
328
			);
329
		}
330
		$master_user = $this->helper_retrieve_local_master_user();
331
332
		if ( user_can( $master_user, 'manage_options' ) ) {
333
			$result = self::passing_test( array( 'name' => $name ) );
334
		} else {
335
			/* translators: a WordPress username */
336
			$connection_error = sprintf( __( 'The user (%s) who setup the Jetpack connection is not an administrator.', 'jetpack' ), $master_user->user_login );
337
			/* translators: a WordPress username */
338
			$recommendation = sprintf( __( 'We recommend either upgrading the user (%s) or reconnecting Jetpack.', 'jetpack' ), $master_user->user_login );
339
340
			$result = self::connection_failing_test( $name, $connection_error, $recommendation );
341
		}
342
343
		return $result;
344
	}
345
346
	/**
347
	 * Test that the PHP's XML library is installed.
348
	 *
349
	 * While it should be installed by default, increasingly in PHP 7, some OSes require an additional php-xml package.
350
	 *
351
	 * @return array Test results.
352
	 */
353
	protected function test__xml_parser_available() {
354
		$name = __FUNCTION__;
355
		if ( function_exists( 'xml_parser_create' ) ) {
356
			$result = self::passing_test( array( 'name' => $name ) );
357
		} else {
358
			$result = self::failing_test(
359
				array(
360
					'name'              => $name,
361
					'label'             => __( 'PHP XML manipulation libraries are not available.', 'jetpack' ),
362
					'short_description' => __( 'Please ask your hosting provider to refer to our server requirements and enable PHP\'s XML module.', 'jetpack' ),
363
					'action_label'      => __( 'View our server requirements', 'jetpack' ),
364
					'action'            => Redirect::get_url( 'jetpack-support-server-requirements' ),
365
				)
366
			);
367
		}
368
		return $result;
369
	}
370
371
	/**
372
	 * Test that the server is able to send an outbound http communication.
373
	 *
374
	 * @return array Test results.
375
	 */
376 View Code Duplication
	protected function test__outbound_http() {
377
		$name    = __FUNCTION__;
378
		$request = wp_remote_get( preg_replace( '/^https:/', 'http:', JETPACK__API_BASE ) . 'test/1/' );
379
		$code    = wp_remote_retrieve_response_code( $request );
380
381
		if ( 200 === (int) $code ) {
382
			$result = self::passing_test( array( 'name' => $name ) );
383
		} else {
384
			$result = self::failing_test(
385
				array(
386
					'name'              => $name,
387
					'short_description' => $this->helper_enable_outbound_requests( 'HTTP' ),
388
				)
389
			);
390
		}
391
392
		return $result;
393
	}
394
395
	/**
396
	 * Test that the server is able to send an outbound https communication.
397
	 *
398
	 * @return array Test results.
399
	 */
400 View Code Duplication
	protected function test__outbound_https() {
401
		$name    = __FUNCTION__;
402
		$request = wp_remote_get( preg_replace( '/^http:/', 'https:', JETPACK__API_BASE ) . 'test/1/' );
403
		$code    = wp_remote_retrieve_response_code( $request );
404
405
		if ( 200 === (int) $code ) {
406
			$result = self::passing_test( array( 'name' => $name ) );
407
		} else {
408
			$result = self::failing_test(
409
				array(
410
					'name'              => $name,
411
					'short_description' => $this->helper_enable_outbound_requests( 'HTTPS' ),
412
				)
413
			);
414
		}
415
416
		return $result;
417
	}
418
419
	/**
420
	 * Check for an IDC.
421
	 *
422
	 * @return array Test results.
423
	 */
424
	protected function test__identity_crisis() {
425
		$name = __FUNCTION__;
426
		if ( ! $this->helper_is_jetpack_connected() ) {
427
			return self::skipped_test(
428
				array(
429
					'name'              => $name,
430
					'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
431
				)
432
			);
433
		}
434
		$identity_crisis = Jetpack::check_identity_crisis();
435
436
		if ( ! $identity_crisis ) {
437
			$result = self::passing_test( array( 'name' => $name ) );
438
		} else {
439
			$result = self::failing_test(
440
				array(
441
					'name'              => $name,
442
					'short_description' => sprintf(
443
						/* translators: Two URLs. The first is the locally-recorded value, the second is the value as recorded on WP.com. */
444
						__( 'Your url is set as `%1$s`, but your WordPress.com connection lists it as `%2$s`!', 'jetpack' ),
445
						$identity_crisis['home'],
446
						$identity_crisis['wpcom_home']
447
					),
448
					'action_label'      => $this->helper_get_support_text(),
449
					'action'            => $this->helper_get_support_url(),
450
				)
451
			);
452
		}
453
		return $result;
454
	}
455
456
	/**
457
	 * Tests blog and current user's token against wp.com's check-token-health endpoint.
458
	 *
459
	 * @since 9.0.0
460
	 *
461
	 * @return array Test results.
462
	 */
463
	protected function test__connection_token_health() {
464
		$name = __FUNCTION__;
465
466
		$m                = new Connection_Manager();
467
		$user_id          = get_current_user_id() ? get_current_user_id() : $m->get_connection_owner_id();
468
		$validated_tokens = $m->validate_tokens( $user_id );
469
470
		if ( ! is_array( $validated_tokens ) || count( array_diff_key( array_flip( array( 'blog_token', 'user_token' ) ), $validated_tokens ) ) ) {
471
			return self::skipped_test(
472
				array(
473
					'name'              => $name,
474
					'short_description' => __( 'Token health check failed to validate tokens.', 'jetpack' ),
475
				)
476
			);
477
		}
478
479
		$invalid_tokens_exist = false;
480
		foreach ( $validated_tokens as $validated_token ) {
481
			if ( ! $validated_token['is_healthy'] ) {
482
				$invalid_tokens_exist = true;
483
				break;
484
			}
485
		}
486
487
		if ( false === $invalid_tokens_exist ) {
488
			return self::passing_test( array( 'name' => $name ) );
489
		}
490
491
		$connection_error = __( 'Invalid Jetpack connection tokens.', 'jetpack' );
492
493
		return self::connection_failing_test( $name, $connection_error );
494
	}
495
496
	/**
497
	 * Tests connection status against wp.com's test-connection endpoint.
498
	 *
499
	 * @todo: Compare with the wpcom_self_test. We only need one of these.
500
	 *
501
	 * @return array Test results.
502
	 */
503
	protected function test__wpcom_connection_test() {
504
		$name = __FUNCTION__;
505
506
		$status = new Status();
507 View Code Duplication
		if ( ! Jetpack::is_active() || $status->is_offline_mode() || $status->is_staging_site() || ! $this->pass ) {
508
			return self::skipped_test( array( 'name' => $name ) );
509
		}
510
511
		add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
512
		$response = Client::wpcom_json_api_request_as_blog(
513
			sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
514
			Client::WPCOM_JSON_API_VERSION
515
		);
516
		remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
517
518
		if ( is_wp_error( $response ) ) {
519
			if ( false !== strpos( $response->get_error_message(), 'cURL error 28' ) ) { // Timeout.
520
				$result = self::skipped_test(
521
					array(
522
						'name'              => $name,
523
						'short_description' => self::helper_get_timeout_text(),
524
					)
525
				);
526
			} else {
527
				/* translators: %1$s is the error code, %2$s is the error message */
528
				$message = sprintf( __( 'Connection test failed (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() );
529
530
				$result = self::connection_failing_test( $name, $message );
531
			}
532
533
			return $result;
534
		}
535
536
		$body = wp_remote_retrieve_body( $response );
537
		if ( ! $body ) {
538
			return self::failing_test(
539
				array(
540
					'name'              => $name,
541
					'short_description' => __( 'Connection test failed (empty response body)', 'jetpack' ) . wp_remote_retrieve_response_code( $response ),
542
					'action_label'      => $this->helper_get_support_text(),
543
					'action'            => $this->helper_get_support_url(),
544
				)
545
			);
546
		}
547
548
		if ( 404 === wp_remote_retrieve_response_code( $response ) ) {
549
			return self::skipped_test(
550
				array(
551
					'name'              => $name,
552
					'short_description' => __( 'The WordPress.com API returned a 404 error.', 'jetpack' ),
553
				)
554
			);
555
		}
556
557
		$result       = json_decode( $body );
558
		$is_connected = ! empty( $result->connected );
559
		$message      = $result->message . ': ' . wp_remote_retrieve_response_code( $response );
560
561
		if ( $is_connected ) {
562
			$res = self::passing_test( array( 'name' => $name ) );
563
		} else {
564
			$res = self::connection_failing_test( $name, $message );
565
		}
566
567
		return $res;
568
	}
569
570
	/**
571
	 * Tests the port number to ensure it is an expected value.
572
	 *
573
	 * We expect that sites on be on one of:
574
	 * port 80,
575
	 * port 443 (https sites only),
576
	 * the value of JETPACK_SIGNATURE__HTTP_PORT,
577
	 * unless the site is intentionally on a different port (e.g. example.com:8080 is the site's URL).
578
	 *
579
	 * If the value isn't one of those and the site's URL doesn't include a port, then the signature verification will fail.
580
	 *
581
	 * This happens most commonly on sites with reverse proxies, so the edge (e.g. Varnish) is running on 80/443, but nginx
582
	 * or Apache is responding internally on a different port (e.g. 81).
583
	 *
584
	 * @return array Test results
585
	 */
586
	protected function test__server_port_value() {
587
		$name = __FUNCTION__;
588
		if ( ! isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) && ! isset( $_SERVER['SERVER_PORT'] ) ) {
589
			return self::skipped_test(
590
				array(
591
					'name'              => $name,
592
					'short_description' => __( 'The server port values are not defined. This is most common when running PHP via a CLI.', 'jetpack' ),
593
				)
594
			);
595
		}
596
		$site_port   = wp_parse_url( home_url(), PHP_URL_PORT );
597
		$server_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? (int) $_SERVER['HTTP_X_FORWARDED_PORT'] : (int) $_SERVER['SERVER_PORT'];
598
		$http_ports  = array( 80 );
599
		$https_ports = array( 80, 443 );
600
601
		if ( defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ) {
602
			$http_ports[] = JETPACK_SIGNATURE__HTTP_PORT;
603
		}
604
605
		if ( defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ) {
606
			$https_ports[] = JETPACK_SIGNATURE__HTTPS_PORT;
607
		}
608
609
		if ( $site_port ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $site_port of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
610
			return self::skipped_test( array( 'name' => $name ) ); // Not currently testing for this situation.
611
		}
612
613
		if ( is_ssl() && in_array( $server_port, $https_ports, true ) ) {
614
			return self::passing_test( array( 'name' => $name ) );
615
		} elseif ( in_array( $server_port, $http_ports, true ) ) {
616
			return self::passing_test( array( 'name' => $name ) );
617
		} else {
618
			if ( is_ssl() ) {
619
				$needed_constant = 'JETPACK_SIGNATURE__HTTPS_PORT';
620
			} else {
621
				$needed_constant = 'JETPACK_SIGNATURE__HTTP_PORT';
622
			}
623
			return self::failing_test(
624
				array(
625
					'name'              => $name,
626
					'short_description' => sprintf(
627
						/* translators: %1$s - a PHP code snippet */
628
						__(
629
							'The server port value is unexpected.
630
						Try adding the following to your wp-config.php file: %1$s',
631
							'jetpack'
632
						),
633
						"define( '$needed_constant', $server_port )"
634
					),
635
				)
636
			);
637
		}
638
	}
639
640
	/**
641
	 * Full Sync Health Test.
642
	 *
643
	 * Sync Disabled: Results in a skipped test
644
	 * Not In Progress : Results in a skipped test
645
	 * In Progress: Results in skipped test w/ status in CLI
646
	 */
647
	protected function test__full_sync_health() {
648
649
		$name = __FUNCTION__;
650
651
		if ( ! $this->helper_is_jetpack_connected() ) {
652
			// If the site is not connected, there is no point in testing Sync health.
653
			return self::skipped_test(
654
				array(
655
					'name'                => $name,
656
					'show_in_site_health' => false,
657
				)
658
			);
659
		}
660
661
		// Sync is enabled.
662
		if ( Sync_Settings::is_sync_enabled() ) {
663
664
			// Get Full Sync Progress.
665
			$full_sync_module = Modules::get_module( 'full-sync' );
666
			$progress_percent = $full_sync_module ? $full_sync_module->get_sync_progress_percentage() : null;
667
668
			// Full Sync in Progress.
669
			if ( $progress_percent ) {
670
671
				return self::informational_test(
672
					array(
673
						'name'              => $name,
674
						'label'             => __( 'Jetpack is performing a full sync of your site', 'jetpack' ),
675
						'severity'          => 'recommended',
676
						/* translators: placeholder is a percentage number. */
677
						'short_description' => sprintf( __( 'Jetpack is performing a full sync of your site. Current Progress: %1$d %%', 'jetpack' ), $progress_percent ),
678
						'long_description'  => sprintf(
679
							'<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>',
680
							__( '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. */
681
							__( 'Updating', 'jetpack' ),
682
							__( 'Jetpack is currently performing a full sync of your site data.', 'jetpack' )
683
						),
684
					)
685
				);
686
687
			} else {
688
689
				// no Full Sync in Progress.
690
				return self::skipped_test(
691
					array(
692
						'name'                => $name,
693
						'show_in_site_health' => false,
694
					)
695
				);
696
697
			}
698
		} else {
699
700
			// If sync is not enabled no Full Sync can occur.
701
			return self::skipped_test(
702
				array(
703
					'name'                => $name,
704
					'show_in_site_health' => false,
705
				)
706
			);
707
708
		}
709
710
	}
711
712
	/**
713
	 * Sync Health Tests.
714
	 *
715
	 * Disabled: Results in a failing test (recommended)
716
	 * Delayed: Results in failing test (recommended)
717
	 * Error: Results in failing test (critical)
718
	 */
719
	protected function test__sync_health() {
720
721
		$name = __FUNCTION__;
722
723
		if ( ! $this->helper_is_jetpack_connected() ) {
724
			// If the site is not connected, there is no point in testing Sync health.
725
			return self::skipped_test(
726
				array(
727
					'name'                => $name,
728
					'show_in_site_health' => false,
729
				)
730
			);
731
		}
732
733
		// Sync is enabled.
734
		if ( Sync_Settings::is_sync_enabled() ) {
735
736
			if ( Sync_Health::get_status() === Sync_Health::STATUS_OUT_OF_SYNC ) {
737
				/*
738
				 * Sync has experienced Data Loss.
739
				 */
740
				$description  = '<p>';
741
				$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' );
742
				$description .= '</p>';
743
				$description .= '<p>';
744
				$description .= sprintf(
745
					'<span class="dashicons fail"><span class="screen-reader-text">%1$s</span></span> ',
746
					esc_html__( 'Error', 'jetpack' )
747
				);
748
				$description .= wp_kses(
749
					__( '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' ),
750
					array(
751
						'a'      => array(
752
							'id'   => array(),
753
							'href' => array(),
754
						),
755
						'strong' => array(),
756
					)
757
				);
758
				$description .= '</p>';
759
760
				return self::failing_test(
761
					array(
762
						'name'              => $name,
763
						'label'             => __( 'Jetpack has detected an error syncing your site.', 'jetpack' ),
764
						'severity'          => 'critical',
765
						'action'            => Redirect::get_url( 'jetpack-contact-support' ),
766
						'action_label'      => __( 'Contact Jetpack Support', 'jetpack' ),
767
						'short_description' => __( 'Jetpack has detected an error while syncing your site. We recommend a full sync to align Jetpack with your site data.', 'jetpack' ),
768
						'long_description'  => $description,
769
					)
770
				);
771
772
			} else {
773
				// Get the Sync Queue.
774
				$sender     = Sync_Sender::get_instance();
775
				$sync_queue = $sender->get_sync_queue();
776
777
				// lag exceeds 5 minutes.
778
				if ( $sync_queue->lag() > 5 * MINUTE_IN_SECONDS ) {
779
780
					$description  = '<p>';
781
					$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' );
782
					$description .= '</p>';
783
					$description .= '<p>';
784
					$description .= sprintf(
785
						'<span class="dashicons dashicons-clock" style="color: orange;"><span class="screen-reader-text">%1$s</span></span> ',
786
						/* translators: name, used to describe a clock icon. */
787
						esc_html__( 'Clock', 'jetpack' )
788
					);
789
					$description .= wp_kses(
790
						sprintf(
791
							/* translators: placeholder is a number of minutes. */
792
							_n(
793
								'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>',
794
								'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>',
795
								(int) ( $sync_queue->lag() / MINUTE_IN_SECONDS ),
796
								'jetpack'
797
							),
798
							number_format_i18n( $sync_queue->lag() / MINUTE_IN_SECONDS )
799
						),
800
						array( 'strong' => array() )
801
					);
802
					$description .= '</p>';
803
804
					return self::informational_test(
805
						array(
806
							'name'              => $name,
807
							'label'             => __( 'Jetpack is experiencing a delay syncing your site.', 'jetpack' ),
808
							'severity'          => 'recommended',
809
							'action'            => null,
810
							'action_label'      => null,
811
							'short_description' => __( 'Jetpack is experiencing a delay syncing your site.', 'jetpack' ),
812
							'long_description'  => $description,
813
						)
814
					);
815
816
				} else {
817
818
					// Sync is Healthy.
819
					return self::passing_test( array( 'name' => $name ) );
820
821
				}
822
			}
823
		} else {
824
			/*
825
			 * Sync is disabled.
826
			 */
827
828
			$description  = '<p>';
829
			$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' );
830
			$description .= '</p>';
831
			$description .= '<p>';
832
			$description .= __( 'Developers may enable / disable syncing using the Sync Settings API.', 'jetpack' );
833
			$description .= '</p>';
834
			$description .= '<p>';
835
			$description .= sprintf(
836
				'<span class="dashicons fail"><span class="screen-reader-text">%1$s</span></span> ',
837
				esc_html__( 'Error', 'jetpack' )
838
			);
839
			$description .= wp_kses(
840
				__( 'Jetpack Sync has been disabled on your site. Without it, certain Jetpack features will not work. <strong>We recommend enabling Sync.</strong>', 'jetpack' ),
841
				array( 'strong' => array() )
842
			);
843
			$description .= '</p>';
844
845
			return self::failing_test(
846
				array(
847
					'name'              => $name,
848
					'label'             => __( 'Jetpack Sync has been disabled on your site.', 'jetpack' ),
849
					'severity'          => 'recommended',
850
					'action'            => 'https://github.com/Automattic/jetpack/blob/master/packages/sync/src/class-settings.php',
851
					'action_label'      => __( 'See Github for more on Sync Settings', 'jetpack' ),
852
					'short_description' => __( 'Jetpack Sync has been disabled on your site.', 'jetpack' ),
853
					'long_description'  => $description,
854
				)
855
			);
856
857
		}
858
859
	}
860
861
	/**
862
	 * Calls to WP.com to run the connection diagnostic testing suite.
863
	 *
864
	 * Intentionally added last as it will be skipped if any local failed conditions exist.
865
	 *
866
	 * @since 7.1.0
867
	 * @since 7.9.0 Timeout waiting for a WP.com response no longer fails the test. Test is marked skipped instead.
868
	 *
869
	 * @return array Test results.
870
	 */
871
	protected function last__wpcom_self_test() {
872
		$name = 'test__wpcom_self_test';
873
874
		$status = new Status();
875 View Code Duplication
		if ( ! Jetpack::is_active() || $status->is_offline_mode() || $status->is_staging_site() || ! $this->pass ) {
876
			return self::skipped_test( array( 'name' => $name ) );
877
		}
878
879
		$self_xml_rpc_url = site_url( 'xmlrpc.php' );
880
881
		$testsite_url = JETPACK__API_BASE . 'testsite/1/?url=';
882
883
		// 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.
884
		add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ), PHP_INT_MAX - 1 );
885
886
		$response = wp_remote_get( $testsite_url . $self_xml_rpc_url );
887
888
		remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ), PHP_INT_MAX - 1 );
889
890
		if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
891
			$result = self::passing_test( array( 'name' => $name ) );
892
		} elseif ( is_wp_error( $response ) && false !== strpos( $response->get_error_message(), 'cURL error 28' ) ) { // Timeout.
893
			$result = self::skipped_test(
894
				array(
895
					'name'              => $name,
896
					'short_description' => self::helper_get_timeout_text(),
897
				)
898
			);
899
		} else {
900
			$result = self::failing_test(
901
				array(
902
					'name'              => $name,
903
					'short_description' => sprintf(
904
						/* translators: %1$s - A debugging url */
905
						__( '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' ),
906
						Redirect::get_url( 'jetpack-support-debug', array( 'query' => 'url=' . rawurlencode( site_url() ) ) )
907
					),
908
					'action_label'      => $this->helper_get_support_text(),
909
					'action'            => $this->helper_get_support_url(),
910
				)
911
			);
912
		}
913
914
		return $result;
915
	}
916
}
917