Completed
Push — add/debugger-package ( efda91 )
by
unknown
15:59 queued 06:31
created

Data::site_status_tests()   C

Complexity

Conditions 10
Paths 2

Size

Total Lines 84

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
nc 2
nop 1
dl 0
loc 84
rs 6.4824
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Jetpack Debug Data for the legacy Jetpack debugger page and the WP 5.2-era Site Health sections.
4
 *
5
 * @package Automattic/jetpack-debugger
6
 */
7
8
namespace Automattic\Jetpack\Debugger;
9
10
use Automattic\Jetpack\Constants;
11
use Automattic\Jetpack\Sync\Modules;
12
use Automattic\Jetpack\Sync\Functions;
13
use Automattic\Jetpack\Sync\Sender;
14
15
/**
16
 * Class Jetpack_Debug_Data
17
 *
18
 * Collect and return debug data for Jetpack.
19
 *
20
 * @since 7.3.0
21
 */
22
class Data {
23
	/**
24
	 * Determine the active plan and normalize it for the debugger results.
25
	 *
26
	 * @since 7.3.0
27
	 *
28
	 * @return string The plan slug.
29
	 */
30
	public static function what_jetpack_plan() {
31
		$plan = \Jetpack_Plan::get();
32
		return ! empty( $plan['class'] ) ? $plan['class'] : 'undefined';
33
	}
34
35
	/**
36
	 * Convert seconds to human readable time.
37
	 *
38
	 * A dedication function instead of using Core functionality to allow for output in seconds.
39
	 *
40
	 * @since 7.3.0
41
	 *
42
	 * @param int $seconds Number of seconds to convert to human time.
43
	 *
44
	 * @return string Human readable time.
45
	 */
46
	public static function seconds_to_time( $seconds ) {
47
		$seconds = intval( $seconds );
48
		$units   = array(
49
			'week'   => WEEK_IN_SECONDS,
50
			'day'    => DAY_IN_SECONDS,
51
			'hour'   => HOUR_IN_SECONDS,
52
			'minute' => MINUTE_IN_SECONDS,
53
			'second' => 1,
54
		);
55
		// specifically handle zero.
56
		if ( 0 === $seconds ) {
57
			return '0 seconds';
58
		}
59
		$human_readable = '';
60
		foreach ( $units as $name => $divisor ) {
61
			$quot = intval( $seconds / $divisor );
62
			if ( $quot ) {
63
				$human_readable .= "$quot $name";
64
				$human_readable .= ( abs( $quot ) > 1 ? 's' : '' ) . ', ';
65
				$seconds        -= $quot * $divisor;
66
			}
67
		}
68
		return substr( $human_readable, 0, -2 );
69
	}
70
71
	/**
72
	 * Return debug data in the format expected by Core's Site Health Info tab.
73
	 *
74
	 * @since 7.3.0
75
	 *
76
	 * @param array $debug {
77
	 *     The debug information already compiled by Core.
78
	 *
79
	 *     @type string  $label        The title for this section of the debug output.
80
	 *     @type string  $description  Optional. A description for your information section which may contain basic HTML
81
	 *                                 markup: `em`, `strong` and `a` for linking to documentation or putting emphasis.
82
	 *     @type boolean $show_count   Optional. If set to `true` the amount of fields will be included in the title for
83
	 *                                 this section.
84
	 *     @type boolean $private      Optional. If set to `true` the section and all associated fields will be excluded
85
	 *                                 from the copy-paste text area.
86
	 *     @type array   $fields {
87
	 *         An associative array containing the data to be displayed.
88
	 *
89
	 *         @type string  $label    The label for this piece of information.
90
	 *         @type string  $value    The output that is of interest for this field.
91
	 *         @type boolean $private  Optional. If set to `true` the field will not be included in the copy-paste text area
92
	 *                                 on top of the page, allowing you to show, for example, API keys here.
93
	 *     }
94
	 * }
95
	 *
96
	 * @return array $args Debug information in the same format as the initial argument.
97
	 */
98
	public static function core_debug_data( $debug ) {
99
		$support_url = \Jetpack::is_development_version()
100
			? 'https://jetpack.com/contact-support/beta-group/'
101
			: 'https://jetpack.com/contact-support/';
102
103
		$jetpack = array(
104
			'jetpack' => array(
105
				'label'       => __( 'Jetpack', 'jetpack' ),
106
				'description' => sprintf(
107
					/* translators: %1$s is URL to jetpack.com's contact support page. %2$s accessibility text */
108
					__(
109
						'Diagnostic information helpful to <a href="%1$s" target="_blank" rel="noopener noreferrer">your Jetpack Happiness team<span class="screen-reader-text">%2$s</span></a>',
110
						'jetpack'
111
					),
112
					esc_url( $support_url ),
113
					__( '(opens in a new tab)', 'jetpack' )
114
				),
115
				'fields'      => self::debug_data(),
116
			),
117
		);
118
		$debug   = array_merge( $debug, $jetpack );
119
		return $debug;
120
	}
121
122
	/**
123
	 * Compile and return array of debug information.
124
	 *
125
	 * @since 7.3.0
126
	 *
127
	 * @return array $args {
128
	 *          Associated array of arrays with the following.
129
	 *         @type string  $label    The label for this piece of information.
130
	 *         @type string  $value    The output that is of interest for this field.
131
	 *         @type boolean $private  Optional. Set to true if data is sensitive (API keys, etc).
132
	 * }
133
	 */
134
	public static function debug_data() {
135
		$debug_info = array();
136
137
		/* Add various important Jetpack options */
138
		$debug_info['site_id']        = array(
139
			'label'   => 'Jetpack Site ID',
140
			'value'   => \Jetpack_Options::get_option( 'id' ),
141
			'private' => false,
142
		);
143
		$debug_info['ssl_cert']       = array(
144
			'label'   => 'Jetpack SSL Verfication Bypass',
145
			'value'   => ( \Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) ? 'Yes' : 'No',
146
			'private' => false,
147
		);
148
		$debug_info['time_diff']      = array(
149
			'label'   => "Offset between Jetpack server's time and this server's time.",
150
			'value'   => \Jetpack_Options::get_option( 'time_diff' ),
151
			'private' => false,
152
		);
153
		$debug_info['version_option'] = array(
154
			'label'   => 'Current Jetpack Version Option',
155
			'value'   => \Jetpack_Options::get_option( 'version' ),
156
			'private' => false,
157
		);
158
		$debug_info['old_version']    = array(
159
			'label'   => 'Previous Jetpack Version',
160
			'value'   => \Jetpack_Options::get_option( 'old_version' ),
161
			'private' => false,
162
		);
163
		$debug_info['public']         = array(
164
			'label'   => 'Jetpack Site Public',
165
			'value'   => ( \Jetpack_Options::get_option( 'public' ) ) ? 'Public' : 'Private',
166
			'private' => false,
167
		);
168
		$debug_info['master_user']    = array(
169
			'label'   => 'Jetpack Master User',
170
			'value'   => self::human_readable_master_user(),
171
			'private' => false,
172
		);
173
174
		/**
175
		 * Token information is private, but awareness if there one is set is helpful.
176
		 *
177
		 * To balance out information vs privacy, we only display and include the "key",
178
		 * which is a segment of the token prior to a period within the token and is
179
		 * technically not private.
180
		 *
181
		 * If a token does not contain a period, then it is malformed and we report it as such.
182
		 */
183
		$user_id    = get_current_user_id();
184
		$blog_token = \Jetpack_Data::get_access_token();
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
185
		$user_token = \Jetpack_Data::get_access_token( $user_id );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
186
187
		$tokenset = '';
188 View Code Duplication
		if ( $blog_token ) {
189
			$tokenset = 'Blog ';
190
			$blog_key = substr( $blog_token->secret, 0, strpos( $blog_token->secret, '.' ) );
191
			// Intentionally not translated since this is helpful when sent to Happiness.
192
			$blog_key = ( $blog_key ) ? $blog_key : 'Potentially Malformed Token.';
193
		}
194 View Code Duplication
		if ( $user_token ) {
195
			$tokenset .= 'User';
196
			$user_key  = substr( $user_token->secret, 0, strpos( $user_token->secret, '.' ) );
197
			// Intentionally not translated since this is helpful when sent to Happiness.
198
			$user_key = ( $user_key ) ? $user_key : 'Potentially Malformed Token.';
199
		}
200
		if ( ! $tokenset ) {
201
			$tokenset = 'None';
202
		}
203
204
		$debug_info['current_user'] = array(
205
			'label'   => 'Current User',
206
			'value'   => self::human_readable_user( $user_id ),
207
			'private' => false,
208
		);
209
		$debug_info['tokens_set']   = array(
210
			'label'   => 'Tokens defined',
211
			'value'   => $tokenset,
212
			'private' => false,
213
		);
214
		$debug_info['blog_token']   = array(
215
			'label'   => 'Blog Public Key',
216
			'value'   => ( $blog_token ) ? $blog_key : 'Not set.',
0 ignored issues
show
Bug introduced by
The variable $blog_key does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
217
			'private' => false,
218
		);
219
		$debug_info['user_token']   = array(
220
			'label'   => 'User Public Key',
221
			'value'   => ( $user_token ) ? $user_key : 'Not set.',
0 ignored issues
show
Bug introduced by
The variable $user_key does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
222
			'private' => false,
223
		);
224
225
		/** Jetpack Environmental Information */
226
		$debug_info['version']       = array(
227
			'label'   => 'Jetpack Version',
228
			'value'   => JETPACK__VERSION,
229
			'private' => false,
230
		);
231
		$debug_info['jp_plugin_dir'] = array(
232
			'label'   => 'Jetpack Directory',
233
			'value'   => JETPACK__PLUGIN_DIR,
234
			'private' => false,
235
		);
236
		$debug_info['plan']          = array(
237
			'label'   => 'Plan Type',
238
			'value'   => self::what_jetpack_plan(),
239
			'private' => false,
240
		);
241
242
		foreach ( array(
243
			'HTTP_HOST',
244
			'SERVER_PORT',
245
			'HTTPS',
246
			'GD_PHP_HANDLER',
247
			'HTTP_AKAMAI_ORIGIN_HOP',
248
			'HTTP_CF_CONNECTING_IP',
249
			'HTTP_CLIENT_IP',
250
			'HTTP_FASTLY_CLIENT_IP',
251
			'HTTP_FORWARDED',
252
			'HTTP_FORWARDED_FOR',
253
			'HTTP_INCAP_CLIENT_IP',
254
			'HTTP_TRUE_CLIENT_IP',
255
			'HTTP_X_CLIENTIP',
256
			'HTTP_X_CLUSTER_CLIENT_IP',
257
			'HTTP_X_FORWARDED',
258
			'HTTP_X_FORWARDED_FOR',
259
			'HTTP_X_IP_TRAIL',
260
			'HTTP_X_REAL_IP',
261
			'HTTP_X_VARNISH',
262
			'REMOTE_ADDR',
263
		) as $header ) {
264
			if ( isset( $_SERVER[ $header ] ) ) {
265
				$debug_info[ $header ] = array(
266
					'label'   => 'Server Variable ' . $header,
267
					'value'   => ( $_SERVER[ $header ] ) ? $_SERVER[ $header ] : 'false',
268
					'private' => false,
269
				);
270
			}
271
		}
272
273
		$debug_info['protect_header'] = array(
274
			'label'   => 'Trusted IP',
275
			'value'   => wp_json_encode( get_site_option( 'trusted_ip_header' ) ),
276
			'private' => false,
277
		);
278
279
		/** Sync Debug Information */
280
		$sync_module = Modules::get_module( 'full-sync' );
281
		if ( $sync_module ) {
282
			$sync_statuses              = $sync_module->get_status();
283
			$human_readable_sync_status = array();
284
			foreach ( $sync_statuses as $sync_status => $sync_status_value ) {
285
				$human_readable_sync_status[ $sync_status ] =
286
					in_array( $sync_status, array( 'started', 'queue_finished', 'send_started', 'finished' ), true )
287
						? gmdate( 'r', $sync_status_value ) : $sync_status_value;
288
			}
289
			$debug_info['full_sync'] = array(
290
				'label'   => 'Full Sync Status',
291
				'value'   => wp_json_encode( $human_readable_sync_status ),
292
				'private' => false,
293
			);
294
		}
295
296
		$queue = Sender::get_instance()->get_sync_queue();
297
298
		$debug_info['sync_size'] = array(
299
			'label'   => 'Sync Queue Size',
300
			'value'   => $queue->size(),
301
			'private' => false,
302
		);
303
		$debug_info['sync_lag']  = array(
304
			'label'   => 'Sync Queue Lag',
305
			'value'   => self::seconds_to_time( $queue->lag() ),
306
			'private' => false,
307
		);
308
309
		$full_sync_queue = Sender::get_instance()->get_full_sync_queue();
310
311
		$debug_info['full_sync_size'] = array(
312
			'label'   => 'Full Sync Queue Size',
313
			'value'   => $full_sync_queue->size(),
314
			'private' => false,
315
		);
316
		$debug_info['full_sync_lag']  = array(
317
			'label'   => 'Full Sync Queue Lag',
318
			'value'   => self::seconds_to_time( $full_sync_queue->lag() ),
319
			'private' => false,
320
		);
321
322
		/**
323
		 * IDC Information
324
		 *
325
		 * Must follow sync debug since it depends on sync functionality.
326
		 */
327
		$idc_urls = array(
328
			'home'       => Functions::home_url(),
329
			'siteurl'    => Functions::site_url(),
330
			'WP_HOME'    => Constants::is_defined( 'WP_HOME' ) ? Constants::get_constant( 'WP_HOME' ) : '',
331
			'WP_SITEURL' => Constants::is_defined( 'WP_SITEURL' ) ? Constants::get_constant( 'WP_SITEURL' ) : '',
332
		);
333
334
		$debug_info['idc_urls']         = array(
335
			'label'   => 'IDC URLs',
336
			'value'   => wp_json_encode( $idc_urls ),
337
			'private' => false,
338
		);
339
		$debug_info['idc_error_option'] = array(
340
			'label'   => 'IDC Error Option',
341
			'value'   => wp_json_encode( \Jetpack_Options::get_option( 'sync_error_idc' ) ),
342
			'private' => false,
343
		);
344
		$debug_info['idc_optin']        = array(
345
			'label'   => 'IDC Opt-in',
346
			'value'   => \Jetpack::sync_idc_optin(),
347
			'private' => false,
348
		);
349
350
		// @todo -- Add testing results?
351
		$cxn_tests               = new Tests();
352
		$debug_info['cxn_tests'] = array(
353
			'label'   => 'Connection Tests',
354
			'value'   => '',
355
			'private' => false,
356
		);
357
		if ( $cxn_tests->pass() ) {
358
			$debug_info['cxn_tests']['value'] = 'All Pass.';
359
		} else {
360
			$debug_info['cxn_tests']['value'] = wp_json_encode( $cxn_tests->list_fails() );
361
		}
362
363
		return $debug_info;
364
	}
365
366
	/**
367
	 * Returns a human readable string for which user is the master user.
368
	 *
369
	 * @return string
370
	 */
371
	private static function human_readable_master_user() {
372
		$master_user = \Jetpack_Options::get_option( 'master_user' );
373
374
		if ( ! $master_user ) {
375
			return __( 'No master user set.', 'jetpack' );
376
		}
377
378
		$user = new \WP_User( $master_user );
379
380
		if ( ! $user ) {
381
			return __( 'Master user no longer exists. Please disconnect and reconnect Jetpack.', 'jetpack' );
382
		}
383
384
		return self::human_readable_user( $user );
385
	}
386
387
	/**
388
	 * Return human readable string for a given user object.
389
	 *
390
	 * @param \WP_User|int $user Object or ID.
391
	 *
392
	 * @return string
393
	 */
394
	private static function human_readable_user( $user ) {
395
		$user = new \WP_User( $user );
396
397
		return sprintf( '#%1$d %2$s (%3$s)', $user->ID, $user->user_login, $user->user_email ); // Format: "#1 username ([email protected])".
398
	}
399
400
	/**
401
	 * Test runner for Core's Site Health module.
402
	 *
403
	 * @since 7.3.0
404
	 */
405
	public static function ajax_local_testing_suite() {
406
		check_ajax_referer( 'health-check-site-status' );
407
		if ( ! current_user_can( 'jetpack_manage_modules' ) ) {
408
			wp_send_json_error();
409
		}
410
		$tests = new Tests();
411
		wp_send_json_success( $tests->output_results_for_core_async_site_health() );
412
	}
413
	/**
414
	 * Adds the Jetpack Local Testing Suite to the Core Site Health system.
415
	 *
416
	 * @since 7.3.0
417
	 *
418
	 * @param array $core_tests Array of tests from Core's Site Health.
419
	 *
420
	 * @return array $core_tests Array of tests for Core's Site Health.
421
	 */
422
	public static function site_status_tests( $core_tests ) {
423
		$cxn_tests = new Tests();
424
		$tests     = $cxn_tests->list_tests( 'direct' );
425
		foreach ( $tests as $test ) {
426
			$core_tests['direct'][ $test['name'] ] = array(
427
				'label' => __( 'Jetpack: ', 'jetpack' ) . $test['name'],
428
				'test'  => function() use ( $test, $cxn_tests ) { // phpcs:ignore PHPCompatibility.FunctionDeclarations.NewClosure.Found
429
					$results = $cxn_tests->run_test( $test['name'] );
430
					// Test names are, by default, `test__some_string_of_text`. Let's convert to "Some String Of Text" for humans.
431
					$label = ucwords(
432
						str_replace(
433
							'_',
434
							' ',
435
							str_replace( 'test__', '', $test['name'] )
436
						)
437
					);
438
					$return = array(
439
						'label'       => $label,
440
						'status'      => 'good',
441
						'badge'       => array(
442
							'label' => __( 'Jetpack', 'jetpack' ),
443
							'color' => 'green',
444
						),
445
						'description' => sprintf(
446
							'<p>%s</p>',
447
							__( 'This test successfully passed!', 'jetpack' )
448
						),
449
						'actions'     => '',
450
						'test'        => 'jetpack_' . $test['name'],
451
					);
452
					if ( is_wp_error( $results ) ) {
453
						return;
454
					}
455
					if ( false === $results['pass'] ) {
456
						$return['label'] = $results['message'];
457
458
						if ( $results['label'] ) {
459
							// Allow tests to override the strange message => label logic with an actual label.
460
							$return['label'] = $results['label'];
461
						}
462
463
						// Most tests pass a `resolution` property to use as a description.
464
						$return['description'] = sprintf(
465
							'<p>%s</p>',
466
							$results['resolution']
467
						);
468
469
						if ( $results['description'] ) {
470
							// Allow tests to override 'resolution' with their own HTML description.
471
							$return['description'] = $results['description'];
472
						}
473
474
						$return['status'] = $results['severity'];
475
						if ( ! empty( $results['action'] ) ) {
476
							$return['actions'] = sprintf(
477
								'<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>',
478
								esc_url( $results['action'] ),
479
								$results['action_label'],
480
								/* translators: accessibility text */
481
								__( '(opens in a new tab)', 'jetpack' )
482
							);
483
						}
484
					} elseif ( true === $results['pass'] ) {
485
						// Passing tests can chose to override defaults.
486
						if ( $results['label'] ) {
487
							$return['label'] = $results['label'];
488
						}
489
490
						if ( $results['description'] ) {
491
							$return['description'] = $results['description'];
492
						}
493
					}
494
495
					return $return;
496
				},
497
			);
498
		}
499
		$core_tests['async']['jetpack_test_suite'] = array(
500
			'label' => __( 'Jetpack Tests', 'jetpack' ),
501
			'test'  => 'jetpack_local_testing_suite',
502
		);
503
504
		return $core_tests;
505
	}
506
}
507