PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.3.2
Jetpack – WP Security, Backup, Speed, & Growth v12.3.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / _inc / lib / debugger / class-jetpack-cxn-tests.php
jetpack / _inc / lib / debugger Last commit date
class-jetpack-cxn-test-base.php 3 years ago class-jetpack-cxn-tests.php 3 years ago class-jetpack-debug-data.php 3 years ago class-jetpack-debugger.php 3 years ago debug-functions.php 3 years ago jetpack-debugger-site-health.css 6 years ago jetpack-debugger-site-health.js 6 years ago
class-jetpack-cxn-tests.php
985 lines
1 <?php
2 /**
3 * Collection of tests to run on the Jetpack connection locally.
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Connection\Client;
9 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
10 use Automattic\Jetpack\Connection\Tokens;
11 use Automattic\Jetpack\Redirect;
12 use Automattic\Jetpack\Status;
13 use Automattic\Jetpack\Sync\Health as Sync_Health;
14 use Automattic\Jetpack\Sync\Modules;
15 use Automattic\Jetpack\Sync\Sender as Sync_Sender;
16 use Automattic\Jetpack\Sync\Settings as Sync_Settings;
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_connection_ready() && ! ( 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 ( new Tokens() )->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 * Returns the url to reconnect Jetpack.
103 *
104 * @return string The reconnect url.
105 */
106 protected static function helper_get_reconnect_url() {
107 return admin_url( 'admin.php?page=jetpack#/reconnect' );
108 }
109
110 /**
111 * Gets translated support text.
112 */
113 protected function helper_get_support_text() {
114 return __( 'Please contact Jetpack support.', 'jetpack' );
115 }
116
117 /**
118 * Returns the translated text to reconnect Jetpack.
119 *
120 * @return string The translated reconnect text.
121 */
122 protected static function helper_get_reconnect_text() {
123 return __( 'Reconnect Jetpack now', 'jetpack' );
124 }
125
126 /**
127 * Returns the translated text for failing tests due to timeouts.
128 *
129 * @return string The translated timeout text.
130 */
131 protected static function helper_get_timeout_text() {
132 return __( 'The test timed out which may sometimes indicate a failure or may be a false failure. Please relaunch tests.', 'jetpack' );
133 }
134
135 /**
136 * Gets translated reconnect long description.
137 *
138 * @param string $connection_error The connection specific error.
139 * @param string $recommendation The recommendation for resolving the connection error.
140 *
141 * @return string The translated long description for reconnection recommendations.
142 */
143 protected static function helper_get_reconnect_long_description( $connection_error, $recommendation ) {
144
145 return sprintf(
146 '<p>%1$s</p>' .
147 '<p><span class="dashicons fail"><span class="screen-reader-text">%2$s</span></span> %3$s</p><p><strong>%4$s</strong></p>',
148 __( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
149 /* translators: screen reader text indicating a test failed */
150 __( 'Error', 'jetpack' ),
151 $connection_error,
152 $recommendation
153 );
154 }
155
156 /**
157 * Helper function to return consistent responses for a connection failing test.
158 *
159 * @param string $name The raw method name that runs the test. Default unnamed_test.
160 * @param string $connection_error The connection specific error. Default 'Your site is not connected to Jetpack.'.
161 * @param string $recommendation The recommendation for resolving the connection error. Default 'We recommend reconnecting Jetpack.'.
162 *
163 * @return array Test results.
164 */
165 public static function connection_failing_test( $name, $connection_error = '', $recommendation = '' ) {
166 $connection_error = empty( $connection_error ) ? __( 'Your site is not connected to Jetpack.', 'jetpack' ) : $connection_error;
167 $recommendation = empty( $recommendation ) ? __( 'We recommend reconnecting Jetpack.', 'jetpack' ) : $recommendation;
168
169 $args = array(
170 'name' => $name,
171 'short_description' => $connection_error,
172 'action' => self::helper_get_reconnect_url(),
173 'action_label' => self::helper_get_reconnect_text(),
174 'long_description' => self::helper_get_reconnect_long_description( $connection_error, $recommendation ),
175 );
176
177 return self::failing_test( $args );
178 }
179
180 /**
181 * Gets translated text to enable outbound requests.
182 *
183 * @param string $protocol Either 'HTTP' or 'HTTPS'.
184 *
185 * @return string The translated text.
186 */
187 protected function helper_enable_outbound_requests( $protocol ) {
188 return sprintf(
189 /* translators: %1$s - request protocol, either http or https */
190 __(
191 'Your server did not successfully connect to the Jetpack server using %1$s
192 Please ask your hosting provider to confirm your server can make outbound requests to jetpack.com.',
193 'jetpack'
194 ),
195 $protocol
196 );
197 }
198
199 /**
200 * Returns 30 for use with a filter.
201 *
202 * To allow time for WP.com to run upstream testing, this function exists to increase the http_request_timeout value
203 * to 30.
204 *
205 * @return int 30
206 */
207 public static function increase_timeout() {
208 return 30; // seconds.
209 }
210
211 /**
212 * The test verifies the blog token exists.
213 *
214 * @return array
215 */
216 protected function test__blog_token_if_exists() {
217 $name = __FUNCTION__;
218
219 if ( ! $this->helper_is_jetpack_connected() ) {
220 return self::skipped_test(
221 array(
222 'name' => $name,
223 'short_description' => __( 'Jetpack is not connected. No blog token to check.', 'jetpack' ),
224 )
225 );
226 }
227 $blog_token = $this->helper_get_blog_token();
228
229 if ( $blog_token ) {
230 $result = self::passing_test( array( 'name' => $name ) );
231 } else {
232 $connection_error = __( 'Blog token is missing.', 'jetpack' );
233
234 $result = self::connection_failing_test( $name, $connection_error );
235 }
236
237 return $result;
238 }
239
240 /**
241 * Test if Jetpack is connected.
242 */
243 protected function test__check_if_connected() {
244 $name = __FUNCTION__;
245
246 if ( ! $this->helper_get_blog_token() ) {
247 return self::skipped_test(
248 array(
249 'name' => $name,
250 'short_description' => __( 'Blog token is missing.', 'jetpack' ),
251 )
252 );
253 }
254
255 if ( $this->helper_is_jetpack_connected() ) {
256 $result = self::passing_test(
257 array(
258 'name' => $name,
259 'label' => __( 'Your site is connected to Jetpack', 'jetpack' ),
260 'long_description' => sprintf(
261 '<p>%1$s</p>' .
262 '<p><span class="dashicons pass"><span class="screen-reader-text">%2$s</span></span> %3$s</p>',
263 __( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
264 /* translators: Screen reader text indicating a test has passed */
265 __( 'Passed', 'jetpack' ),
266 __( 'Your site is connected to Jetpack.', 'jetpack' )
267 ),
268 )
269 );
270 } elseif ( ( new Status() )->is_offline_mode() ) {
271 $result = self::skipped_test(
272 array(
273 'name' => $name,
274 'short_description' => __( 'Jetpack is in Offline Mode:', 'jetpack' ) . ' ' . Jetpack::development_mode_trigger_text(),
275 )
276 );
277 } else {
278 $connection_error = __( 'Your site is not connected to Jetpack', 'jetpack' );
279
280 $result = self::connection_failing_test( $name, $connection_error );
281 }
282
283 return $result;
284 }
285
286 /**
287 * Test that the master user still exists on this site.
288 *
289 * @return array Test results.
290 */
291 protected function test__master_user_exists_on_site() {
292 $name = __FUNCTION__;
293 if ( ! $this->helper_is_jetpack_connected() ) {
294 return self::skipped_test(
295 array(
296 'name' => $name,
297 'short_description' => __( 'Jetpack is not connected. No master user to check.', 'jetpack' ),
298 )
299 );
300 }
301 if ( ! ( new Connection_Manager() )->get_connection_owner_id() ) {
302 return self::skipped_test(
303 array(
304 'name' => $name,
305 'short_description' => __( 'Jetpack is running without a connected user. No master user to check.', 'jetpack' ),
306 )
307 );
308 }
309 $local_user = $this->helper_retrieve_local_master_user();
310
311 if ( $local_user->exists() ) {
312 $result = self::passing_test( array( 'name' => $name ) );
313 } else {
314 $connection_error = __( 'The user who setup the Jetpack connection no longer exists on this site.', 'jetpack' );
315
316 $result = self::connection_failing_test( $name, $connection_error );
317 }
318
319 return $result;
320 }
321
322 /**
323 * Test that the master user has the manage options capability (e.g. is an admin).
324 *
325 * Generic calls from WP.com execute on Jetpack as the master user. If it isn't an admin, random things will fail.
326 *
327 * @return array Test results.
328 */
329 protected function test__master_user_can_manage_options() {
330 $name = __FUNCTION__;
331 if ( ! $this->helper_is_jetpack_connected() ) {
332 return self::skipped_test(
333 array(
334 'name' => $name,
335 'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
336 )
337 );
338 }
339 if ( ! ( new Connection_Manager() )->get_connection_owner_id() ) {
340 return self::skipped_test(
341 array(
342 'name' => $name,
343 'short_description' => __( 'Jetpack is running without a connected user. No master user to check.', 'jetpack' ),
344 )
345 );
346 }
347 $master_user = $this->helper_retrieve_local_master_user();
348
349 if ( user_can( $master_user, 'manage_options' ) ) {
350 $result = self::passing_test( array( 'name' => $name ) );
351 } else {
352 /* translators: a WordPress username */
353 $connection_error = sprintf( __( 'The user (%s) who setup the Jetpack connection is not an administrator.', 'jetpack' ), $master_user->user_login );
354 /* translators: a WordPress username */
355 $recommendation = sprintf( __( 'We recommend either upgrading the user (%s) or reconnecting Jetpack.', 'jetpack' ), $master_user->user_login );
356
357 $result = self::connection_failing_test( $name, $connection_error, $recommendation );
358 }
359
360 return $result;
361 }
362
363 /**
364 * Test that the PHP's XML library is installed.
365 *
366 * While it should be installed by default, increasingly in PHP 7, some OSes require an additional php-xml package.
367 *
368 * @return array Test results.
369 */
370 protected function test__xml_parser_available() {
371 $name = __FUNCTION__;
372 if ( function_exists( 'xml_parser_create' ) ) {
373 $result = self::passing_test( array( 'name' => $name ) );
374 } else {
375 $result = self::failing_test(
376 array(
377 'name' => $name,
378 'label' => __( 'PHP XML manipulation libraries are not available.', 'jetpack' ),
379 'short_description' => __( 'Please ask your hosting provider to refer to our server requirements and enable PHP\'s XML module.', 'jetpack' ),
380 'action_label' => __( 'View our server requirements', 'jetpack' ),
381 'action' => Redirect::get_url( 'jetpack-support-server-requirements' ),
382 )
383 );
384 }
385 return $result;
386 }
387
388 /**
389 * Test that the server is able to send an outbound http communication.
390 *
391 * @return array Test results.
392 */
393 protected function test__outbound_http() {
394 $name = __FUNCTION__;
395 $request = wp_remote_get( preg_replace( '/^https:/', 'http:', JETPACK__API_BASE ) . 'test/1/' );
396 $code = wp_remote_retrieve_response_code( $request );
397
398 if ( 200 === (int) $code ) {
399 $result = self::passing_test( array( 'name' => $name ) );
400 } else {
401 $result = self::failing_test(
402 array(
403 'name' => $name,
404 'short_description' => $this->helper_enable_outbound_requests( 'HTTP' ),
405 )
406 );
407 }
408
409 return $result;
410 }
411
412 /**
413 * Test that the server is able to send an outbound https communication.
414 *
415 * @return array Test results.
416 */
417 protected function test__outbound_https() {
418 $name = __FUNCTION__;
419 $request = wp_remote_get( preg_replace( '/^http:/', 'https:', JETPACK__API_BASE ) . 'test/1/' );
420 $code = wp_remote_retrieve_response_code( $request );
421
422 if ( 200 === (int) $code ) {
423 $result = self::passing_test( array( 'name' => $name ) );
424 } else {
425 $result = self::failing_test(
426 array(
427 'name' => $name,
428 'short_description' => $this->helper_enable_outbound_requests( 'HTTPS' ),
429 )
430 );
431 }
432
433 return $result;
434 }
435
436 /**
437 * Check for an IDC.
438 *
439 * @return array Test results.
440 */
441 protected function test__identity_crisis() {
442 $name = __FUNCTION__;
443 if ( ! $this->helper_is_jetpack_connected() ) {
444 return self::skipped_test(
445 array(
446 'name' => $name,
447 'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
448 )
449 );
450 }
451 $identity_crisis = Jetpack::check_identity_crisis();
452
453 if ( ! $identity_crisis ) {
454 $result = self::passing_test( array( 'name' => $name ) );
455 } else {
456 $result = self::failing_test(
457 array(
458 'name' => $name,
459 'short_description' => sprintf(
460 /* translators: Two URLs. The first is the locally-recorded value, the second is the value as recorded on WP.com. */
461 __( 'Your url is set as `%1$s`, but your WordPress.com connection lists it as `%2$s`!', 'jetpack' ),
462 $identity_crisis['home'],
463 $identity_crisis['wpcom_home']
464 ),
465 'action_label' => $this->helper_get_support_text(),
466 'action' => $this->helper_get_support_url(),
467 )
468 );
469 }
470 return $result;
471 }
472
473 /**
474 * Tests the health of the Connection tokens.
475 *
476 * This will always check the blog token health. It will also check the user token health if
477 * a user is logged in and connected, or if there's a connected owner.
478 *
479 * @since 9.0.0
480 * @since 9.6.0 Checks only blog token if current user not connected or site does not have a connected owner.
481 *
482 * @return array Test results.
483 */
484 protected function test__connection_token_health() {
485 $name = __FUNCTION__;
486 $m = new Connection_Manager();
487 $user_id = get_current_user_id();
488
489 // Check if there's a connected logged in user.
490 if ( $user_id && ! $m->is_user_connected( $user_id ) ) {
491 $user_id = false;
492 }
493
494 // If no logged in user to check, let's see if there's a master_user set.
495 if ( ! $user_id ) {
496 $user_id = Jetpack_Options::get_option( 'master_user' );
497 if ( $user_id && ! $m->is_user_connected( $user_id ) ) {
498 return self::connection_failing_test( $name, __( 'Missing token for the connection owner.', 'jetpack' ) );
499 }
500 }
501
502 if ( $user_id ) {
503 return $this->check_tokens_health( $user_id );
504 } else {
505 return $this->check_blog_token_health();
506 }
507 }
508
509 /**
510 * Tests blog and user's token against wp.com's check-token-health endpoint.
511 *
512 * @since 9.6.0
513 *
514 * @return array Test results.
515 */
516 protected function check_blog_token_health() {
517 $name = 'test__connection_token_health';
518 $valid = ( new Tokens() )->validate_blog_token();
519
520 if ( ! $valid ) {
521 return self::connection_failing_test( $name, __( 'Blog token validation failed.', 'jetpack' ) );
522 } else {
523 return self::passing_test( array( 'name' => $name ) );
524 }
525 }
526
527 /**
528 * Tests blog token against wp.com's check-token-health endpoint.
529 *
530 * @since 9.6.0
531 *
532 * @param int $user_id The user ID to check the tokens for.
533 *
534 * @return array Test results.
535 */
536 protected function check_tokens_health( $user_id ) {
537 $name = 'test__connection_token_health';
538 $validated_tokens = ( new Tokens() )->validate( $user_id );
539
540 if ( ! is_array( $validated_tokens ) || count( array_diff_key( array_flip( array( 'blog_token', 'user_token' ) ), $validated_tokens ) ) ) {
541 return self::skipped_test(
542 array(
543 'name' => $name,
544 'short_description' => __( 'Token health check failed to validate tokens.', 'jetpack' ),
545 )
546 );
547 }
548
549 $invalid_tokens_exist = false;
550 foreach ( $validated_tokens as $validated_token ) {
551 if ( ! $validated_token['is_healthy'] ) {
552 $invalid_tokens_exist = true;
553 break;
554 }
555 }
556
557 if ( false === $invalid_tokens_exist ) {
558 return self::passing_test( array( 'name' => $name ) );
559 }
560
561 $connection_error = __( 'Invalid Jetpack connection tokens.', 'jetpack' );
562
563 return self::connection_failing_test( $name, $connection_error );
564 }
565
566 /**
567 * Tests connection status against wp.com's test-connection endpoint.
568 *
569 * @todo: Compare with the wpcom_self_test. We only need one of these.
570 *
571 * @return array Test results.
572 */
573 protected function test__wpcom_connection_test() {
574 $name = __FUNCTION__;
575
576 $status = new Status();
577 if ( ! Jetpack::is_connection_ready() || $status->is_offline_mode() || $status->is_staging_site() || ! $this->pass ) {
578 return self::skipped_test( array( 'name' => $name ) );
579 }
580
581 add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
582 $response = Client::wpcom_json_api_request_as_blog(
583 sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
584 Client::WPCOM_JSON_API_VERSION
585 );
586 remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
587
588 if ( is_wp_error( $response ) ) {
589 if ( false !== strpos( $response->get_error_message(), 'cURL error 28' ) ) { // Timeout.
590 $result = self::skipped_test(
591 array(
592 'name' => $name,
593 'short_description' => self::helper_get_timeout_text(),
594 )
595 );
596 } else {
597 /* translators: %1$s is the error code, %2$s is the error message */
598 $message = sprintf( __( 'Connection test failed (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() );
599
600 $result = self::connection_failing_test( $name, $message );
601 }
602
603 return $result;
604 }
605
606 $body = wp_remote_retrieve_body( $response );
607 if ( ! $body ) {
608 return self::failing_test(
609 array(
610 'name' => $name,
611 'short_description' => __( 'Connection test failed (empty response body)', 'jetpack' ) . wp_remote_retrieve_response_code( $response ),
612 'action_label' => $this->helper_get_support_text(),
613 'action' => $this->helper_get_support_url(),
614 )
615 );
616 }
617
618 if ( 404 === wp_remote_retrieve_response_code( $response ) ) {
619 return self::skipped_test(
620 array(
621 'name' => $name,
622 'short_description' => __( 'The WordPress.com API returned a 404 error.', 'jetpack' ),
623 )
624 );
625 }
626
627 $result = json_decode( $body );
628 $is_connected = ! empty( $result->connected );
629 $message = $result->message . ': ' . wp_remote_retrieve_response_code( $response );
630
631 if ( $is_connected ) {
632 $res = self::passing_test( array( 'name' => $name ) );
633 } else {
634 $res = self::connection_failing_test( $name, $message );
635 }
636
637 return $res;
638 }
639
640 /**
641 * Tests the port number to ensure it is an expected value.
642 *
643 * We expect that sites on be on one of:
644 * port 80,
645 * port 443 (https sites only),
646 * the value of JETPACK_SIGNATURE__HTTP_PORT,
647 * unless the site is intentionally on a different port (e.g. example.com:8080 is the site's URL).
648 *
649 * If the value isn't one of those and the site's URL doesn't include a port, then the signature verification will fail.
650 *
651 * This happens most commonly on sites with reverse proxies, so the edge (e.g. Varnish) is running on 80/443, but nginx
652 * or Apache is responding internally on a different port (e.g. 81).
653 *
654 * @return array Test results
655 */
656 protected function test__server_port_value() {
657 $name = __FUNCTION__;
658 if ( ! isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) && ! isset( $_SERVER['SERVER_PORT'] ) ) {
659 return self::skipped_test(
660 array(
661 'name' => $name,
662 'short_description' => __( 'The server port values are not defined. This is most common when running PHP via a CLI.', 'jetpack' ),
663 )
664 );
665 }
666 $site_port = wp_parse_url( home_url(), PHP_URL_PORT );
667 $server_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? (int) $_SERVER['HTTP_X_FORWARDED_PORT'] : (int) $_SERVER['SERVER_PORT'];
668 $http_ports = array( 80 );
669 $https_ports = array( 80, 443 );
670
671 if ( defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ) {
672 $http_ports[] = JETPACK_SIGNATURE__HTTP_PORT;
673 }
674
675 if ( defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ) {
676 $https_ports[] = JETPACK_SIGNATURE__HTTPS_PORT;
677 }
678
679 if ( $site_port ) {
680 return self::skipped_test( array( 'name' => $name ) ); // Not currently testing for this situation.
681 }
682
683 if ( is_ssl() && in_array( $server_port, $https_ports, true ) ) {
684 return self::passing_test( array( 'name' => $name ) );
685 } elseif ( in_array( $server_port, $http_ports, true ) ) {
686 return self::passing_test( array( 'name' => $name ) );
687 } else {
688 if ( is_ssl() ) {
689 $needed_constant = 'JETPACK_SIGNATURE__HTTPS_PORT';
690 } else {
691 $needed_constant = 'JETPACK_SIGNATURE__HTTP_PORT';
692 }
693 return self::failing_test(
694 array(
695 'name' => $name,
696 'short_description' => sprintf(
697 /* translators: %1$s - a PHP code snippet */
698 __(
699 'The server port value is unexpected.
700 Try adding the following to your wp-config.php file: %1$s',
701 'jetpack'
702 ),
703 "define( '$needed_constant', $server_port )"
704 ),
705 )
706 );
707 }
708 }
709
710 /**
711 * Full Sync Health Test.
712 *
713 * Sync Disabled: Results in a skipped test
714 * Not In Progress : Results in a skipped test
715 * In Progress: Results in skipped test w/ status in CLI
716 */
717 protected function test__full_sync_health() {
718
719 $name = __FUNCTION__;
720
721 if ( ! $this->helper_is_jetpack_connected() ) {
722 // If the site is not connected, there is no point in testing Sync health.
723 return self::skipped_test(
724 array(
725 'name' => $name,
726 'show_in_site_health' => false,
727 )
728 );
729 }
730
731 // Sync is enabled.
732 if ( Sync_Settings::is_sync_enabled() ) {
733
734 // Get Full Sync Progress.
735 $full_sync_module = Modules::get_module( 'full-sync' );
736 $progress_percent = $full_sync_module ? $full_sync_module->get_sync_progress_percentage() : null;
737
738 // Full Sync in Progress.
739 if ( $progress_percent ) {
740
741 return self::informational_test(
742 array(
743 'name' => $name,
744 'label' => __( 'Jetpack is performing a full sync of your site', 'jetpack' ),
745 'severity' => 'recommended',
746 /* translators: placeholder is a percentage number. */
747 'short_description' => sprintf( __( 'Jetpack is performing a full sync of your site. Current Progress: %1$d %%', 'jetpack' ), $progress_percent ),
748 'long_description' => sprintf(
749 '<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>',
750 __( '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. */
751 __( 'Updating', 'jetpack' ),
752 __( 'Jetpack is currently performing a full sync of your site data.', 'jetpack' )
753 ),
754 )
755 );
756
757 } else {
758
759 // no Full Sync in Progress.
760 return self::skipped_test(
761 array(
762 'name' => $name,
763 'show_in_site_health' => false,
764 )
765 );
766
767 }
768 } else {
769
770 // If sync is not enabled no Full Sync can occur.
771 return self::skipped_test(
772 array(
773 'name' => $name,
774 'show_in_site_health' => false,
775 )
776 );
777
778 }
779 }
780
781 /**
782 * Sync Health Tests.
783 *
784 * Disabled: Results in a failing test (recommended)
785 * Delayed: Results in failing test (recommended)
786 * Error: Results in failing test (critical)
787 */
788 protected function test__sync_health() {
789
790 $name = __FUNCTION__;
791
792 if ( ! $this->helper_is_jetpack_connected() ) {
793 // If the site is not connected, there is no point in testing Sync health.
794 return self::skipped_test(
795 array(
796 'name' => $name,
797 'show_in_site_health' => false,
798 )
799 );
800 }
801
802 // Sync is enabled.
803 if ( Sync_Settings::is_sync_enabled() ) {
804
805 if ( Sync_Health::get_status() === Sync_Health::STATUS_OUT_OF_SYNC ) {
806 /*
807 * Sync has experienced Data Loss.
808 */
809 $description = '<p>';
810 $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' );
811 $description .= '</p>';
812 $description .= '<p>';
813 $description .= sprintf(
814 '<span class="dashicons fail"><span class="screen-reader-text">%1$s</span></span> ',
815 esc_html__( 'Error', 'jetpack' )
816 );
817 $description .= wp_kses(
818 __( 'Jetpack has detected that data is not properly in sync which may be impacting some of your site’s functionality. <strong>Click <a id="full_sync_request_link" href="#">here</a> to start a fix</strong> to align Jetpack with your site data. If you still notice this error after running the fix process, please contact support for additional assistance.', 'jetpack' ),
819 array(
820 'a' => array(
821 'id' => array(),
822 'href' => array(),
823 ),
824 'strong' => array(),
825 )
826 );
827 $description .= '</p>';
828
829 return self::failing_test(
830 array(
831 'name' => $name,
832 'label' => __( 'Jetpack has detected an error syncing your site.', 'jetpack' ),
833 'severity' => 'critical',
834 'action' => Redirect::get_url( 'jetpack-contact-support' ),
835 'action_label' => __( 'Contact Jetpack Support', 'jetpack' ),
836 'short_description' => __( 'Jetpack has detected that data is not properly in sync which may be impacting some of your site’s functionality. We recommend performing a fix to align Jetpack with your site data. If you still notice this error after running the fix process, please contact support for additional assistance.', 'jetpack' ),
837 'long_description' => $description,
838 )
839 );
840
841 } else {
842 // Get the Sync Queue.
843 $sender = Sync_Sender::get_instance();
844 $sync_queue = $sender->get_sync_queue();
845
846 // lag exceeds 5 minutes.
847 if ( $sync_queue->lag() > 5 * MINUTE_IN_SECONDS ) {
848
849 $description = '<p>';
850 $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' );
851 $description .= '</p>';
852 $description .= '<p>';
853 $description .= sprintf(
854 '<span class="dashicons dashicons-clock" style="color: orange;"><span class="screen-reader-text">%1$s</span></span> ',
855 /* translators: name, used to describe a clock icon. */
856 esc_html__( 'Clock', 'jetpack' )
857 );
858 $description .= wp_kses(
859 sprintf(
860 /* translators: placeholder is a number of minutes. */
861 _n(
862 '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>',
863 '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>',
864 (int) ( $sync_queue->lag() / MINUTE_IN_SECONDS ),
865 'jetpack'
866 ),
867 number_format_i18n( $sync_queue->lag() / MINUTE_IN_SECONDS )
868 ),
869 array( 'strong' => array() )
870 );
871 $description .= '</p>';
872
873 return self::informational_test(
874 array(
875 'name' => $name,
876 'label' => __( 'Jetpack is experiencing a delay syncing your site.', 'jetpack' ),
877 'severity' => 'recommended',
878 'action' => null,
879 'action_label' => null,
880 'short_description' => __( 'Jetpack is experiencing a delay syncing your site.', 'jetpack' ),
881 'long_description' => $description,
882 )
883 );
884
885 } else {
886
887 // Sync is Healthy.
888 return self::passing_test( array( 'name' => $name ) );
889
890 }
891 }
892 } else {
893 /*
894 * Sync is disabled.
895 */
896
897 $description = '<p>';
898 $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' );
899 $description .= '</p>';
900 $description .= '<p>';
901 $description .= __( 'Developers may enable / disable syncing using the Sync Settings API.', 'jetpack' );
902 $description .= '</p>';
903 $description .= '<p>';
904 $description .= sprintf(
905 '<span class="dashicons fail"><span class="screen-reader-text">%1$s</span></span> ',
906 esc_html__( 'Error', 'jetpack' )
907 );
908 $description .= wp_kses(
909 __( 'Jetpack Sync has been disabled on your site. Without it, certain Jetpack features will not work. <strong>We recommend enabling Sync.</strong>', 'jetpack' ),
910 array( 'strong' => array() )
911 );
912 $description .= '</p>';
913
914 return self::failing_test(
915 array(
916 'name' => $name,
917 'label' => __( 'Jetpack Sync has been disabled on your site.', 'jetpack' ),
918 'severity' => 'recommended',
919 'action' => 'https://github.com/Automattic/jetpack/blob/trunk/projects/packages/sync/src/class-settings.php',
920 'action_label' => __( 'See Github for more on Sync Settings', 'jetpack' ),
921 'short_description' => __( 'Jetpack Sync has been disabled on your site.', 'jetpack' ),
922 'long_description' => $description,
923 )
924 );
925
926 }
927 }
928
929 /**
930 * Calls to WP.com to run the connection diagnostic testing suite.
931 *
932 * Intentionally added last as it will be skipped if any local failed conditions exist.
933 *
934 * @since 7.1.0
935 * @since 7.9.0 Timeout waiting for a WP.com response no longer fails the test. Test is marked skipped instead.
936 *
937 * @return array Test results.
938 */
939 protected function last__wpcom_self_test() {
940 $name = 'test__wpcom_self_test';
941
942 $status = new Status();
943 if ( ! Jetpack::is_connection_ready() || $status->is_offline_mode() || $status->is_staging_site() || ! $this->pass ) {
944 return self::skipped_test( array( 'name' => $name ) );
945 }
946
947 $self_xml_rpc_url = site_url( 'xmlrpc.php' );
948
949 $testsite_url = JETPACK__API_BASE . 'testsite/1/?url=';
950
951 // 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.
952 add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ), PHP_INT_MAX - 1 );
953
954 $response = wp_remote_get( $testsite_url . $self_xml_rpc_url );
955
956 remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ), PHP_INT_MAX - 1 );
957
958 if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
959 $result = self::passing_test( array( 'name' => $name ) );
960 } elseif ( is_wp_error( $response ) && false !== strpos( $response->get_error_message(), 'cURL error 28' ) ) { // Timeout.
961 $result = self::skipped_test(
962 array(
963 'name' => $name,
964 'short_description' => self::helper_get_timeout_text(),
965 )
966 );
967 } else {
968 $result = self::failing_test(
969 array(
970 'name' => $name,
971 'short_description' => sprintf(
972 /* translators: %1$s - A debugging url */
973 __( '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' ),
974 Redirect::get_url( 'jetpack-support-debug', array( 'query' => 'url=' . rawurlencode( site_url() ) ) )
975 ),
976 'action_label' => $this->helper_get_support_text(),
977 'action' => $this->helper_get_support_url(),
978 )
979 );
980 }
981
982 return $result;
983 }
984 }
985