Completed
Push — instant-search-master ( a786a9...893b7c )
by
unknown
85:32 queued 79:17
created

helper_enable_outbound_requests()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 11
rs 9.9
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\Status;
10
use Automattic\Jetpack\Connection\Utils as Connection_Utils;
11
use Automattic\Jetpack\Sync\Settings as Sync_Settings;
12
13
/**
14
 * Class Jetpack_Cxn_Tests contains all of the actual tests.
15
 */
16
class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base {
17
18
	/**
19
	 * Jetpack_Cxn_Tests constructor.
20
	 */
21
	public function __construct() {
22
		parent::__construct();
23
24
		$methods = get_class_methods( 'Jetpack_Cxn_Tests' );
25
26
		foreach ( $methods as $method ) {
27
			if ( false === strpos( $method, 'test__' ) ) {
28
				continue;
29
			}
30
			$this->add_test( array( $this, $method ), $method, 'direct' );
31
		}
32
33
		/**
34
		 * Fires after loading default Jetpack Connection tests.
35
		 *
36
		 * @since 7.1.0
37
		 * @since 8.3.0 Passes the Jetpack_Cxn_Tests instance.
38
		 */
39
		do_action( 'jetpack_connection_tests_loaded', $this );
40
41
		/**
42
		 * Determines if the WP.com testing suite should be included.
43
		 *
44
		 * @since 7.1.0
45
		 * @since 8.1.0 Default false.
46
		 *
47
		 * @param bool $run_test To run the WP.com testing suite. Default false.
48
		 */
49
		if ( apply_filters( 'jetpack_debugger_run_self_test', false ) ) {
50
			/**
51
			 * Intentionally added last as it checks for an existing failure state before attempting.
52
			 * Generally, any failed location condition would result in the WP.com check to fail too, so
53
			 * we will skip it to avoid confusing error messages.
54
			 *
55
			 * Note: This really should be an 'async' test.
56
			 */
57
			$this->add_test( array( $this, 'last__wpcom_self_test' ), 'test__wpcom_self_test', 'direct' );
58
		}
59
	}
60
61
	/**
62
	 * Helper function to look up the expected master user and return the local WP_User.
63
	 *
64
	 * @return WP_User Jetpack's expected master user.
65
	 */
66
	protected function helper_retrieve_local_master_user() {
67
		$master_user = Jetpack_Options::get_option( 'master_user' );
68
		return new WP_User( $master_user );
69
	}
70
71
	/**
72
	 * Is Jetpack even connected and supposed to be talking to WP.com?
73
	 */
74
	protected function helper_is_jetpack_connected() {
75
		return ( Jetpack::is_active() && ! ( new Status() )->is_development_mode() );
76
	}
77
78
	/**
79
	 * Returns a support url based on development mode.
80
	 */
81
	protected function helper_get_support_url() {
82
		return Jetpack::is_development_version()
83
			? 'https://jetpack.com/contact-support/beta-group/'
84
			: 'https://jetpack.com/contact-support/';
85
	}
86
87
	/**
88
	 * Gets translated support text.
89
	 */
90
	protected function helper_get_support_text() {
91
		return __( 'Please contact Jetpack support.', 'jetpack' );
92
	}
93
94
	/**
95
	 * Gets translated text to enable outbound requests.
96
	 *
97
	 * @param string $protocol Either 'HTTP' or 'HTTPS'.
98
	 *
99
	 * @return string The translated text.
100
	 */
101
	protected function helper_enable_outbound_requests( $protocol ) {
102
		return sprintf(
103
			/* translators: %1$s - request protocol, either http or https */
104
			__(
105
				'Your server did not successfully connect to the Jetpack server using %1$s
106
				Please ask your hosting provider to confirm your server can make outbound requests to jetpack.com.',
107
				'jetpack'
108
			),
109
			$protocol
110
		);
111
	}
112
113
	/**
114
	 * Returns 30 for use with a filter.
115
	 *
116
	 * To allow time for WP.com to run upstream testing, this function exists to increase the http_request_timeout value
117
	 * to 30.
118
	 *
119
	 * @return int 30
120
	 */
121
	public static function increase_timeout() {
122
		return 30; // seconds.
123
	}
124
125
	/**
126
	 * Test if Jetpack is connected.
127
	 */
128
	protected function test__check_if_connected() {
129
		$name = __FUNCTION__;
130
		if ( $this->helper_is_jetpack_connected() ) {
131
			$result = self::passing_test(
132
				array(
133
					'name'             => $name,
134
					'label'            => __( 'Your site is connected to Jetpack', 'jetpack' ),
135
					'long_description' => sprintf(
136
						'<p>%1$s</p>' .
137
						'<p><span class="dashicons pass"><span class="screen-reader-text">%2$s</span></span> %3$s</p>',
138
						__( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
139
						/* translators: Screen reader text indicating a test has passed */
140
						__( 'Passed', 'jetpack' ),
141
						__( 'Your site is connected to Jetpack.', 'jetpack' )
142
					),
143
				)
144
			);
145
		} elseif ( ( new Status() )->is_development_mode() ) {
146
			$result = self::skipped_test(
147
				array(
148
					'name'              => $name,
149
					'short_description' => __( 'Jetpack is in Development Mode:', 'jetpack' ) . ' ' . Jetpack::development_mode_trigger_text(),
150
				)
151
			);
152
		} else {
153
			$result = self::failing_test(
154
				array(
155
					'name'             => $name,
156
					'label'            => __( 'Your site is not connected to Jetpack', 'jetpack' ),
157
					'action'           => admin_url( 'admin.php?page=jetpack#/dashboard' ),
158
					'action_label'     => __( 'Reconnect your site now', 'jetpack' ),
159
					'long_description' => sprintf(
160
						'<p>%1$s</p>' .
161
						'<p><span class="dashicons fail"><span class="screen-reader-text">%2$s</span></span> %3$s<strong> %4$s</strong></p>',
162
						__( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
163
						/* translators: screen reader text indicating a test failed */
164
						__( 'Error', 'jetpack' ),
165
						__( 'Your site is not connected to Jetpack.', 'jetpack' ),
166
						__( 'We recommend reconnecting Jetpack.', 'jetpack' )
167
					),
168
				)
169
			);
170
		}
171
172
		return $result;
173
	}
174
175
	/**
176
	 * Test that the master user still exists on this site.
177
	 *
178
	 * @return array Test results.
179
	 */
180
	protected function test__master_user_exists_on_site() {
181
		$name = __FUNCTION__;
182 View Code Duplication
		if ( ! $this->helper_is_jetpack_connected() ) {
183
			return self::skipped_test(
184
				array(
185
					'name'              => $name,
186
					'short_description' => __( 'Jetpack is not connected. No master user to check.', 'jetpack' ),
187
				)
188
			);
189
		}
190
		$local_user = $this->helper_retrieve_local_master_user();
191
192
		if ( $local_user->exists() ) {
193
			$result = self::passing_test( array( 'name' => $name ) );
194 View Code Duplication
		} else {
195
			$result = self::failing_test(
196
				array(
197
					'name'              => $name,
198
					'short_description' => __( 'The user who setup the Jetpack connection no longer exists on this site.', 'jetpack' ),
199
					'action_label'      => __( 'Please disconnect and reconnect Jetpack.', 'jetpack' ),
200
					'action'            => 'https://jetpack.com/support/reconnecting-reinstalling-jetpack/',
201
				)
202
			);
203
		}
204
205
		return $result;
206
	}
207
208
	/**
209
	 * Test that the master user has the manage options capability (e.g. is an admin).
210
	 *
211
	 * Generic calls from WP.com execute on Jetpack as the master user. If it isn't an admin, random things will fail.
212
	 *
213
	 * @return array Test results.
214
	 */
215
	protected function test__master_user_can_manage_options() {
216
		$name = __FUNCTION__;
217 View Code Duplication
		if ( ! $this->helper_is_jetpack_connected() ) {
218
			return self::skipped_test(
219
				array(
220
					'name'              => $name,
221
					'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
222
				)
223
			);
224
		}
225
		$master_user = $this->helper_retrieve_local_master_user();
226
227
		if ( user_can( $master_user, 'manage_options' ) ) {
228
			$result = self::passing_test( array( 'name' => $name ) );
229 View Code Duplication
		} else {
230
			$result = self::failing_test(
231
				array(
232
					'name'              => $name,
233
					/* translators: a WordPress username */
234
					'short_description' => sprintf( __( 'The user (%s) who setup the Jetpack connection is not an administrator.', 'jetpack' ), $master_user->user_login ),
235
					'action_label'      => __( 'Either upgrade the user or disconnect and reconnect Jetpack.', 'jetpack' ),
236
					'action'            => 'https://jetpack.com/support/reconnecting-reinstalling-jetpack/',
237
				)
238
			);
239
		}
240
241
		return $result;
242
	}
243
244
	/**
245
	 * Test that the PHP's XML library is installed.
246
	 *
247
	 * While it should be installed by default, increasingly in PHP 7, some OSes require an additional php-xml package.
248
	 *
249
	 * @return array Test results.
250
	 */
251
	protected function test__xml_parser_available() {
252
		$name = __FUNCTION__;
253
		if ( function_exists( 'xml_parser_create' ) ) {
254
			$result = self::passing_test( array( 'name' => $name ) );
255
		} else {
256
			$result = self::failing_test(
257
				array(
258
					'name'              => $name,
259
					'label'             => __( 'PHP XML manipulation libraries are not available.', 'jetpack' ),
260
					'short_description' => __( 'Please ask your hosting provider to refer to our server requirements and enable PHP\'s XML module.', 'jetpack' ),
261
					'action_label'      => __( 'View our server requirements', 'jetpack' ),
262
					'action'            => 'https://jetpack.com/support/server-requirements/',
263
				)
264
			);
265
		}
266
		return $result;
267
	}
268
269
	/**
270
	 * Test that the server is able to send an outbound http communication.
271
	 *
272
	 * @return array Test results.
273
	 */
274 View Code Duplication
	protected function test__outbound_http() {
275
		$name    = __FUNCTION__;
276
		$request = wp_remote_get( preg_replace( '/^https:/', 'http:', JETPACK__API_BASE ) . 'test/1/' );
277
		$code    = wp_remote_retrieve_response_code( $request );
278
279
		if ( 200 === intval( $code ) ) {
280
			$result = self::passing_test( array( 'name' => $name ) );
281
		} else {
282
			$result = self::failing_test(
283
				array(
284
					'name'              => $name,
285
					'short_description' => $this->helper_enable_outbound_requests( 'HTTP' ),
286
				)
287
			);
288
		}
289
290
		return $result;
291
	}
292
293
	/**
294
	 * Test that the server is able to send an outbound https communication.
295
	 *
296
	 * @return array Test results.
297
	 */
298 View Code Duplication
	protected function test__outbound_https() {
299
		$name    = __FUNCTION__;
300
		$request = wp_remote_get( preg_replace( '/^http:/', 'https:', JETPACK__API_BASE ) . 'test/1/' );
301
		$code    = wp_remote_retrieve_response_code( $request );
302
303
		if ( 200 === intval( $code ) ) {
304
			$result = self::passing_test( array( 'name' => $name ) );
305
		} else {
306
			$result = self::failing_test(
307
				array(
308
					'name'              => $name,
309
					'short_description' => $this->helper_enable_outbound_requests( 'HTTPS' ),
310
				)
311
			);
312
		}
313
314
		return $result;
315
	}
316
317
	/**
318
	 * Check for an IDC.
319
	 *
320
	 * @return array Test results.
321
	 */
322
	protected function test__identity_crisis() {
323
		$name = __FUNCTION__;
324 View Code Duplication
		if ( ! $this->helper_is_jetpack_connected() ) {
325
			return self::skipped_test(
326
				array(
327
					'name'              => $name,
328
					'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
329
				)
330
			);
331
		}
332
		$identity_crisis = Jetpack::check_identity_crisis();
333
334
		if ( ! $identity_crisis ) {
335
			$result = self::passing_test( array( 'name' => $name ) );
336
		} else {
337
			$result = self::failing_test(
338
				array(
339
					'name'              => $name,
340
					'short_description' => sprintf(
341
						/* translators: Two URLs. The first is the locally-recorded value, the second is the value as recorded on WP.com. */
342
						__( 'Your url is set as `%1$s`, but your WordPress.com connection lists it as `%2$s`!', 'jetpack' ),
343
						$identity_crisis['home'],
344
						$identity_crisis['wpcom_home']
345
					),
346
					'action_label'      => $this->helper_get_support_text(),
347
					'action'            => $this->helper_get_support_url(),
348
				)
349
			);
350
		}
351
		return $result;
352
	}
353
354
	/**
355
	 * Tests connection status against wp.com's test-connection endpoint.
356
	 *
357
	 * @todo: Compare with the wpcom_self_test. We only need one of these.
358
	 *
359
	 * @return array Test results.
360
	 */
361
	protected function test__wpcom_connection_test() {
362
		$name = __FUNCTION__;
363
364
		$status = new Status();
365 View Code Duplication
		if ( ! Jetpack::is_active() || $status->is_development_mode() || $status->is_staging_site() || ! $this->pass ) {
366
			return self::skipped_test( array( 'name' => $name ) );
367
		}
368
369
		add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
370
		$response = Client::wpcom_json_api_request_as_blog(
371
			sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
372
			Client::WPCOM_JSON_API_VERSION
373
		);
374
		remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
375
376 View Code Duplication
		if ( is_wp_error( $response ) ) {
377
			return self::failing_test(
378
				array(
379
					'name'              => $name,
380
					/* translators: %1$s is the error code, %2$s is the error message */
381
					'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...
382
					'action_label'      => $this->helper_get_support_text(),
383
					'action'            => $this->helper_get_support_url(),
384
				)
385
			);
386
		}
387
388
		$body = wp_remote_retrieve_body( $response );
389 View Code Duplication
		if ( ! $body ) {
390
			return self::failing_test(
391
				array(
392
					'name'              => $name,
393
					'short_description' => __( 'Connection test failed (empty response body)', 'jetpack' ) . wp_remote_retrieve_response_code( $response ),
394
					'action_label'      => $this->helper_get_support_text(),
395
					'action'            => $this->helper_get_support_url(),
396
				)
397
			);
398
		}
399
400 View Code Duplication
		if ( 404 === wp_remote_retrieve_response_code( $response ) ) {
401
			return self::skipped_test(
402
				array(
403
					'name'              => $name,
404
					'short_description' => __( 'The WordPress.com API returned a 404 error.', 'jetpack' ),
405
				)
406
			);
407
		}
408
409
		$result       = json_decode( $body );
410
		$is_connected = (bool) $result->connected;
411
		$message      = $result->message . ': ' . wp_remote_retrieve_response_code( $response );
412
413
		if ( $is_connected ) {
414
			return self::passing_test( array( 'name' => $name ) );
415
		} else {
416
			return self::failing_test(
417
				array(
418
					'name'              => $name,
419
					'short_description' => $message,
420
					'action_label'      => $this->helper_get_support_text(),
421
					'action'            => $this->helper_get_support_url(),
422
				)
423
			);
424
		}
425
	}
426
427
	/**
428
	 * Tests the port number to ensure it is an expected value.
429
	 *
430
	 * We expect that sites on be on one of:
431
	 * port 80,
432
	 * port 443 (https sites only),
433
	 * the value of JETPACK_SIGNATURE__HTTP_PORT,
434
	 * unless the site is intentionally on a different port (e.g. example.com:8080 is the site's URL).
435
	 *
436
	 * If the value isn't one of those and the site's URL doesn't include a port, then the signature verification will fail.
437
	 *
438
	 * This happens most commonly on sites with reverse proxies, so the edge (e.g. Varnish) is running on 80/443, but nginx
439
	 * or Apache is responding internally on a different port (e.g. 81).
440
	 *
441
	 * @return array Test results
442
	 */
443
	protected function test__server_port_value() {
444
		$name = __FUNCTION__;
445
		if ( ! isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) && ! isset( $_SERVER['SERVER_PORT'] ) ) {
446
			return self::skipped_test(
447
				array(
448
					'name'              => $name,
449
					'short_description' => __( 'The server port values are not defined. This is most common when running PHP via a CLI.', 'jetpack' ),
450
				)
451
			);
452
		}
453
		$site_port   = wp_parse_url( home_url(), PHP_URL_PORT );
454
		$server_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? (int) $_SERVER['HTTP_X_FORWARDED_PORT'] : (int) $_SERVER['SERVER_PORT'];
455
		$http_ports  = array( 80 );
456
		$https_ports = array( 80, 443 );
457
458
		if ( defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ) {
459
			$http_ports[] = JETPACK_SIGNATURE__HTTP_PORT;
460
		}
461
462
		if ( defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ) {
463
			$https_ports[] = JETPACK_SIGNATURE__HTTPS_PORT;
464
		}
465
466
		if ( $site_port ) {
467
			return self::skipped_test( array( 'name' => $name ) ); // Not currently testing for this situation.
468
		}
469
470
		if ( is_ssl() && in_array( $server_port, $https_ports, true ) ) {
471
			return self::passing_test( array( 'name' => $name ) );
472
		} elseif ( in_array( $server_port, $http_ports, true ) ) {
473
			return self::passing_test( array( 'name' => $name ) );
474
		} else {
475
			if ( is_ssl() ) {
476
				$needed_constant = 'JETPACK_SIGNATURE__HTTPS_PORT';
477
			} else {
478
				$needed_constant = 'JETPACK_SIGNATURE__HTTP_PORT';
479
			}
480
			return self::failing_test(
481
				array(
482
					'name'              => $name,
483
					'short_description' => sprintf(
484
						/* translators: %1$s - a PHP code snippet */
485
						__(
486
							'The server port value is unexpected.
487
						Try adding the following to your wp-config.php file: %1$s',
488
							'jetpack'
489
						),
490
						"define( '$needed_constant', $server_port )"
491
					),
492
				)
493
			);
494
		}
495
	}
496
497
	/**
498
	 * If Sync is enabled, this test will be skipped. If Sync is disabled, the test will fail.
499
	 * Eventually, we'll make this test more robust with additional states. Here is the plan for possible Sync states,
500
	 * including states that are planned but not yet implemented.
501
	 *
502
	 * Enabled: Skips test
503
	 * Disabled: Results in a failing test
504
	 * Healthy: @todo
505
	 * In Progress: @todo
506
	 * Delayed: @todo
507
	 * Error: @todo
508
	 */
509
	protected function test__sync_health() {
510
		$name = __FUNCTION__;
511
		if ( ! $this->helper_is_jetpack_connected() ) {
512
			// If the site is not connected, there is no point in testing Sync health.
513
			return self::skipped_test( array( 'name' => $name, 'show_in_site_health' => false ) );
514
		}
515
		if ( Sync_Settings::is_sync_enabled() ) {
516
			return self::skipped_test( array( 'name' => $name ) );
517
		}
518
		return self::failing_test( array(
519
			'name' => $name,
520
			'label' => __( 'Jetpack Sync has been disabled on your site.', 'jetpack' ),
521
			'severity' => 'recommended',
522
			'action' => 'https://github.com/Automattic/jetpack/blob/master/packages/sync/src/class-settings.php',
523
			'action_label' => __( 'See Github for more on Sync Settings', 'jetpack' ),
524
			'short_description' => __( 'Jetpack Sync has been disabled on your site.', 'jetpack' ),
525
			'long_description' => sprintf(
526
				'<p>%1$s</p>' .
527
				'<p>%2$s</p>' .
528
				'<p><span class="dashicons fail"><span class="screen-reader-text">%3$s</span></span> %4$s<strong> %5$s</strong></p>',
529
				__( 'The information synced by Jetpack ensures that Jetpack Search, Related Posts and other features are aligned with your site’s current content.', 'jetpack' ),
530
				__( 'Developers may enable / disable syncing using the Sync Settings API.', 'jetpack' ),
531
				/* translators: screen reader text indicating a test failed */
532
				__( 'Error', 'jetpack' ),
533
				__( 'Jetpack Sync has been disabled on your site. Without it, certain Jetpack features will not work.', 'jetpack' ),
534
				__( 'We recommend enabling Sync.', 'jetpack' )
535
			)
536
		) );
537
	}
538
539
	/**
540
	 * Calls to WP.com to run the connection diagnostic testing suite.
541
	 *
542
	 * Intentionally added last as it will be skipped if any local failed conditions exist.
543
	 *
544
	 * @since 7.1.0
545
	 * @since 7.9.0 Timeout waiting for a WP.com response no longer fails the test. Test is marked skipped instead.
546
	 *
547
	 * @return array Test results.
548
	 */
549
	protected function last__wpcom_self_test() {
550
		$name = 'test__wpcom_self_test';
551
552
		$status = new Status();
553 View Code Duplication
		if ( ! Jetpack::is_active() || $status->is_development_mode() || $status->is_staging_site() || ! $this->pass ) {
554
			return self::skipped_test( array( 'name' => $name ) );
555
		}
556
557
		$self_xml_rpc_url = site_url( 'xmlrpc.php' );
558
559
		$testsite_url = Connection_Utils::fix_url_for_bad_hosts( JETPACK__API_BASE . 'testsite/1/?url=' );
560
561
		add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
562
563
		$response = wp_remote_get( $testsite_url . $self_xml_rpc_url );
564
565
		remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
566
567
		if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
568
			return self::passing_test( array( 'name' => $name ) );
569
		} elseif ( is_wp_error( $response ) && false !== strpos( $response->get_error_message(), 'cURL error 28' ) ) { // Timeout.
570
			return self::skipped_test(
571
				array(
572
					'name'              => $name,
573
					'short_description' => __( 'The test timed out which may sometimes indicate a failure or may be a false failure.', 'jetpack' ),
574
				)
575
			);
576
		} else {
577
			return self::failing_test(
578
				array(
579
					'name'              => $name,
580
					'short_description' => sprintf(
581
						/* translators: %1$s - A debugging url */
582
						__( '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' ),
583
						esc_url( add_query_arg( 'url', rawurlencode( site_url() ), 'https://jetpack.com/support/debug/' ) )
584
					),
585
					'action_label'      => $this->helper_get_support_text(),
586
					'action'            => $this->helper_get_support_url(),
587
				)
588
			);
589
		}
590
	}
591
}
592