init
This commit is contained in:
@@ -0,0 +1,549 @@
|
||||
<?php
|
||||
/**
|
||||
* Base class for Jetpack's debugging tests.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Status;
|
||||
|
||||
/**
|
||||
* Jetpack Connection Testing
|
||||
*
|
||||
* Framework for various "unit tests" against the Jetpack connection.
|
||||
*
|
||||
* Individual tests should be added to the class-jetpack-cxn-tests.php file.
|
||||
*
|
||||
* @author Brandon Kraft
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
/**
|
||||
* "Unit Tests" for the Jetpack connection.
|
||||
*
|
||||
* @since 7.1.0
|
||||
*/
|
||||
class Jetpack_Cxn_Test_Base {
|
||||
|
||||
/**
|
||||
* Tests to run on the Jetpack connection.
|
||||
*
|
||||
* @var array $tests
|
||||
*/
|
||||
protected $tests = array();
|
||||
|
||||
/**
|
||||
* Results of the Jetpack connection tests.
|
||||
*
|
||||
* @var array $results
|
||||
*/
|
||||
protected $results = array();
|
||||
|
||||
/**
|
||||
* Status of the testing suite.
|
||||
*
|
||||
* Used internally to determine if a test should be skipped since the tests are already failing. Assume passing.
|
||||
*
|
||||
* @var bool $pass
|
||||
*/
|
||||
protected $pass = true;
|
||||
|
||||
/**
|
||||
* Jetpack_Cxn_Test constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->tests = array();
|
||||
$this->results = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new test to the Jetpack Connection Testing suite.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @since 7.3.0 Adds name parameter and returns WP_Error on failure.
|
||||
*
|
||||
* @param callable $callable Test to add to queue.
|
||||
* @param string $name Unique name for the test.
|
||||
* @param string $type Optional. Core Site Health type: 'direct' if test can be run during initial load or 'async' if test should run async.
|
||||
* @param array $groups Optional. Testing groups to add test to.
|
||||
*
|
||||
* @return mixed True if successfully added. WP_Error on failure.
|
||||
*/
|
||||
public function add_test( $callable, $name, $type = 'direct', $groups = array( 'default' ) ) {
|
||||
if ( is_array( $name ) ) {
|
||||
// Pre-7.3.0 method passed the $groups parameter here.
|
||||
return new WP_Error( __( 'add_test arguments changed in 7.3.0. Please reference inline documentation.', 'jetpack' ) );
|
||||
}
|
||||
if ( array_key_exists( $name, $this->tests ) ) {
|
||||
return new WP_Error( __( 'Test names must be unique.', 'jetpack' ) );
|
||||
}
|
||||
if ( ! is_callable( $callable ) ) {
|
||||
return new WP_Error( __( 'Tests must be valid PHP callables.', 'jetpack' ) );
|
||||
}
|
||||
|
||||
$this->tests[ $name ] = array(
|
||||
'name' => $name,
|
||||
'test' => $callable,
|
||||
'group' => $groups,
|
||||
'type' => $type,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all tests to run.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*
|
||||
* @param string $type Optional. Core Site Health type: 'direct' or 'async'. All by default.
|
||||
* @param string $group Optional. A specific testing group. All by default.
|
||||
*
|
||||
* @return array $tests Array of tests with test information.
|
||||
*/
|
||||
public function list_tests( $type = 'all', $group = 'all' ) {
|
||||
if ( ! ( 'all' === $type || 'direct' === $type || 'async' === $type ) ) {
|
||||
_doing_it_wrong( 'Jetpack_Cxn_Test_Base->list_tests', 'Type must be all, direct, or async', '7.3.0' );
|
||||
}
|
||||
|
||||
$tests = array();
|
||||
foreach ( $this->tests as $name => $value ) {
|
||||
// Get all valid tests by group staged.
|
||||
if ( 'all' === $group || $group === $value['group'] ) {
|
||||
$tests[ $name ] = $value;
|
||||
}
|
||||
|
||||
// Next filter out any that do not match the type.
|
||||
if ( 'all' !== $type && $type !== $value['type'] ) {
|
||||
unset( $tests[ $name ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a specific test.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*
|
||||
* @param string $name Name of test.
|
||||
*
|
||||
* @return mixed $result Test result array or WP_Error if invalid name. {
|
||||
* @type string $name Test name
|
||||
* @type mixed $pass True if passed, false if failed, 'skipped' if skipped.
|
||||
* @type string $message Human-readable test result message.
|
||||
* @type string $resolution Human-readable resolution steps.
|
||||
* }
|
||||
*/
|
||||
public function run_test( $name ) {
|
||||
if ( array_key_exists( $name, $this->tests ) ) {
|
||||
return call_user_func( $this->tests[ $name ]['test'] );
|
||||
}
|
||||
return new WP_Error( __( 'There is no test by that name: ', 'jetpack' ) . $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the Jetpack connection suite.
|
||||
*/
|
||||
public function run_tests() {
|
||||
foreach ( $this->tests as $test ) {
|
||||
$result = call_user_func( $test['test'] );
|
||||
$result['group'] = $test['group'];
|
||||
$result['type'] = $test['type'];
|
||||
$this->results[] = $result;
|
||||
if ( false === $result['pass'] ) {
|
||||
$this->pass = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full results array.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @since 7.3.0 Add 'type'
|
||||
*
|
||||
* @param string $type Test type, async or direct.
|
||||
* @param string $group Testing group whose results we want. Defaults to all tests.
|
||||
* @return array Array of test results.
|
||||
*/
|
||||
public function raw_results( $type = 'all', $group = 'all' ) {
|
||||
if ( ! $this->results ) {
|
||||
$this->run_tests();
|
||||
}
|
||||
|
||||
$results = $this->results;
|
||||
|
||||
if ( 'all' !== $group ) {
|
||||
foreach ( $results as $test => $result ) {
|
||||
if ( ! in_array( $group, $result['group'], true ) ) {
|
||||
unset( $results[ $test ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'all' !== $type ) {
|
||||
foreach ( $results as $test => $result ) {
|
||||
if ( $type !== $result['type'] ) {
|
||||
unset( $results[ $test ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status of the connection suite.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @since 7.3.0 Add 'type'
|
||||
*
|
||||
* @param string $type Test type, async or direct. Optional, direct all tests.
|
||||
* @param string $group Testing group to check status of. Optional, default all tests.
|
||||
*
|
||||
* @return true|array True if all tests pass. Array of failed tests.
|
||||
*/
|
||||
public function pass( $type = 'all', $group = 'all' ) {
|
||||
$results = $this->raw_results( $type, $group );
|
||||
|
||||
foreach ( $results as $result ) {
|
||||
// 'pass' could be true, false, or 'skipped'. We only want false.
|
||||
if ( isset( $result['pass'] ) && false === $result['pass'] ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of failed test messages.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @since 7.3.0 Add 'type'
|
||||
*
|
||||
* @param string $type Test type, direct or async.
|
||||
* @param string $group Testing group whose failures we want. Defaults to "all".
|
||||
*
|
||||
* @return false|array False if no failed tests. Otherwise, array of failed tests.
|
||||
*/
|
||||
public function list_fails( $type = 'all', $group = 'all' ) {
|
||||
$results = $this->raw_results( $type, $group );
|
||||
|
||||
foreach ( $results as $test => $result ) {
|
||||
// We do not want tests that passed or ones that are misconfigured (no pass status or no failure message).
|
||||
if ( ! isset( $result['pass'] ) || false !== $result['pass'] || ! isset( $result['short_description'] ) ) {
|
||||
unset( $results[ $test ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return consistent responses for a passing test.
|
||||
* Possible Args:
|
||||
* - name: string The raw method name that runs the test. Default 'unnamed_test'.
|
||||
* - label: bool|string If false, tests will be labeled with their `name`. You can pass a string to override this behavior. Default false.
|
||||
* - short_description: bool|string A brief, non-html description that will appear in CLI results. Default 'Test passed!'.
|
||||
* - long_description: bool|string An html description that will appear in the site health page. Default false.
|
||||
* - severity: bool|string 'critical', 'recommended', or 'good'. Default: false.
|
||||
* - action: bool|string A URL for the recommended action. Default: false
|
||||
* - action_label: bool|string The label for the recommended action. Default: false
|
||||
* - show_in_site_health: bool True if the test should be shown on the Site Health page. Default: true
|
||||
*
|
||||
* @param array $args Arguments to override defaults.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
public static function passing_test( $args ) {
|
||||
$defaults = self::test_result_defaults();
|
||||
$defaults['short_description'] = __( 'Test passed!', 'jetpack' );
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
|
||||
$args['pass'] = true;
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return consistent responses for a skipped test.
|
||||
* Possible Args:
|
||||
* - name: string The raw method name that runs the test. Default unnamed_test.
|
||||
* - label: bool|string If false, tests will be labeled with their `name`. You can pass a string to override this behavior. Default false.
|
||||
* - short_description: bool|string A brief, non-html description that will appear in CLI results, and as headings in admin UIs. Default false.
|
||||
* - long_description: bool|string An html description that will appear in the site health page. Default false.
|
||||
* - severity: bool|string 'critical', 'recommended', or 'good'. Default: false.
|
||||
* - action: bool|string A URL for the recommended action. Default: false
|
||||
* - action_label: bool|string The label for the recommended action. Default: false
|
||||
* - show_in_site_health: bool True if the test should be shown on the Site Health page. Default: true
|
||||
*
|
||||
* @param array $args Arguments to override defaults.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
public static function skipped_test( $args = array() ) {
|
||||
$args = wp_parse_args(
|
||||
$args,
|
||||
self::test_result_defaults()
|
||||
);
|
||||
|
||||
$args['pass'] = 'skipped';
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return consistent responses for an informational test.
|
||||
* Possible Args:
|
||||
* - name: string The raw method name that runs the test. Default unnamed_test.
|
||||
* - label: bool|string If false, tests will be labeled with their `name`. You can pass a string to override this behavior. Default false.
|
||||
* - short_description: bool|string A brief, non-html description that will appear in CLI results, and as headings in admin UIs. Default false.
|
||||
* - long_description: bool|string An html description that will appear in the site health page. Default false.
|
||||
* - severity: bool|string 'critical', 'recommended', or 'good'. Default: false.
|
||||
* - action: bool|string A URL for the recommended action. Default: false
|
||||
* - action_label: bool|string The label for the recommended action. Default: false
|
||||
* - show_in_site_health: bool True if the test should be shown on the Site Health page. Default: true
|
||||
*
|
||||
* @param array $args Arguments to override defaults.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
public static function informational_test( $args = array() ) {
|
||||
$args = wp_parse_args(
|
||||
$args,
|
||||
self::test_result_defaults()
|
||||
);
|
||||
|
||||
$args['pass'] = 'informational';
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return consistent responses for a failing test.
|
||||
* Possible Args:
|
||||
* - name: string The raw method name that runs the test. Default unnamed_test.
|
||||
* - label: bool|string If false, tests will be labeled with their `name`. You can pass a string to override this behavior. Default false.
|
||||
* - short_description: bool|string A brief, non-html description that will appear in CLI results, and as headings in admin UIs. Default 'Test failed!'.
|
||||
* - long_description: bool|string An html description that will appear in the site health page. Default false.
|
||||
* - severity: bool|string 'critical', 'recommended', or 'good'. Default: 'critical'.
|
||||
* - action: bool|string A URL for the recommended action. Default: false.
|
||||
* - action_label: bool|string The label for the recommended action. Default: false.
|
||||
* - show_in_site_health: bool True if the test should be shown on the Site Health page. Default: true
|
||||
*
|
||||
* @since 7.1.0
|
||||
*
|
||||
* @param array $args Arguments to override defaults.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
public static function failing_test( $args ) {
|
||||
$defaults = self::test_result_defaults();
|
||||
$defaults['short_description'] = __( 'Test failed!', 'jetpack' );
|
||||
$defaults['severity'] = 'critical';
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
|
||||
$args['pass'] = false;
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides defaults for test arguments.
|
||||
*
|
||||
* @since 8.5.0
|
||||
*
|
||||
* @return array Result defaults.
|
||||
*/
|
||||
private static function test_result_defaults() {
|
||||
return array(
|
||||
'name' => 'unnamed_test',
|
||||
'label' => false,
|
||||
'short_description' => false,
|
||||
'long_description' => false,
|
||||
'severity' => false,
|
||||
'action' => false,
|
||||
'action_label' => false,
|
||||
'show_in_site_health' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide WP_CLI friendly testing results.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @since 7.3.0 Add 'type'
|
||||
*
|
||||
* @param string $type Test type, direct or async.
|
||||
* @param string $group Testing group whose results we are outputting. Default all tests.
|
||||
*/
|
||||
public function output_results_for_cli( $type = 'all', $group = 'all' ) {
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
if ( ( new Status() )->is_offline_mode() ) {
|
||||
WP_CLI::line( __( 'Jetpack is in Offline Mode:', 'jetpack' ) );
|
||||
WP_CLI::line( Jetpack::development_mode_trigger_text() );
|
||||
}
|
||||
WP_CLI::line( __( 'TEST RESULTS:', 'jetpack' ) );
|
||||
foreach ( $this->raw_results( $group ) as $test ) {
|
||||
if ( true === $test['pass'] ) {
|
||||
WP_CLI::log( WP_CLI::colorize( '%gPassed:%n ' . $test['name'] ) );
|
||||
} elseif ( 'skipped' === $test['pass'] ) {
|
||||
WP_CLI::log( WP_CLI::colorize( '%ySkipped:%n ' . $test['name'] ) );
|
||||
if ( $test['short_description'] ) {
|
||||
WP_CLI::log( ' ' . $test['short_description'] ); // Number of spaces to "tab indent" the reason.
|
||||
}
|
||||
} elseif ( 'informational' === $test['pass'] ) {
|
||||
WP_CLI::log( WP_CLI::colorize( '%yInfo:%n ' . $test['name'] ) );
|
||||
if ( $test['short_description'] ) {
|
||||
WP_CLI::log( ' ' . $test['short_description'] ); // Number of spaces to "tab indent" the reason.
|
||||
}
|
||||
} else { // Failed.
|
||||
WP_CLI::log( WP_CLI::colorize( '%rFailed:%n ' . $test['name'] ) );
|
||||
WP_CLI::log( ' ' . $test['short_description'] ); // Number of spaces to "tab indent" the reason.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output results of failures in format expected by Core's Site Health tool for async tests.
|
||||
*
|
||||
* Specifically not asking for a testing group since we're opinionated that Site Heath should see all.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*
|
||||
* @return array Array of test results
|
||||
*/
|
||||
public function output_results_for_core_async_site_health() {
|
||||
$result = array(
|
||||
'label' => __( 'Jetpack passed all async tests.', 'jetpack' ),
|
||||
'status' => 'good',
|
||||
'badge' => array(
|
||||
'label' => __( 'Jetpack', 'jetpack' ),
|
||||
'color' => 'green',
|
||||
),
|
||||
'description' => sprintf(
|
||||
'<p>%s</p>',
|
||||
__( "Jetpack's async local testing suite passed all tests!", 'jetpack' )
|
||||
),
|
||||
'actions' => '',
|
||||
'test' => 'jetpack_debugger_local_testing_suite_core',
|
||||
);
|
||||
|
||||
if ( $this->pass() ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$fails = $this->list_fails( 'async' );
|
||||
$error = false;
|
||||
foreach ( $fails as $fail ) {
|
||||
if ( ! $error ) {
|
||||
$error = true;
|
||||
$result['label'] = $fail['message'];
|
||||
$result['status'] = $fail['severity'];
|
||||
$result['description'] = sprintf(
|
||||
'<p>%s</p>',
|
||||
$fail['resolution']
|
||||
);
|
||||
if ( ! empty( $fail['action'] ) ) {
|
||||
$result['actions'] = sprintf(
|
||||
'<a class="button button-primary" 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>',
|
||||
esc_url( $fail['action'] ),
|
||||
__( 'Resolve', 'jetpack' ),
|
||||
/* translators: accessibility text */
|
||||
__( '(opens in a new tab)', 'jetpack' )
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$result['description'] .= sprintf(
|
||||
'<p>%s</p>',
|
||||
__( 'There was another problem:', 'jetpack' )
|
||||
) . ' ' . $fail['message'] . ': ' . $fail['resolution'];
|
||||
if ( 'critical' === $fail['severity'] ) { // In case the initial failure is only "recommended".
|
||||
$result['status'] = 'critical';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide single WP Error instance of all failures.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @since 7.3.0 Add 'type'
|
||||
*
|
||||
* @param string $type Test type, direct or async.
|
||||
* @param string $group Testing group whose failures we want converted. Default all tests.
|
||||
*
|
||||
* @return WP_Error|false WP_Error with all failed tests or false if there were no failures.
|
||||
*/
|
||||
public function output_fails_as_wp_error( $type = 'all', $group = 'all' ) {
|
||||
if ( $this->pass( $group ) ) {
|
||||
return false;
|
||||
}
|
||||
$fails = $this->list_fails( $type, $group );
|
||||
$error = false;
|
||||
|
||||
foreach ( $fails as $result ) {
|
||||
$code = 'failed_' . $result['name'];
|
||||
$message = $result['short_description'];
|
||||
$data = array(
|
||||
'resolution' => $result['action'] ?
|
||||
$result['action_label'] . ' :' . $result['action'] :
|
||||
'',
|
||||
);
|
||||
if ( ! $error ) {
|
||||
$error = new WP_Error( $code, $message, $data );
|
||||
} else {
|
||||
$error->add( $code, $message, $data );
|
||||
}
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt data for sending to WordPress.com.
|
||||
*
|
||||
* @param string $data Data to encrypt with the WP.com Public Key.
|
||||
*
|
||||
* @return false|array False if functionality not available. Array of encrypted data, encryption key.
|
||||
*/
|
||||
public function encrypt_string_for_wpcom( $data ) {
|
||||
$return = false;
|
||||
if ( ! function_exists( 'openssl_get_publickey' ) || ! function_exists( 'openssl_seal' ) ) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
$public_key = openssl_get_publickey( JETPACK__DEBUGGER_PUBLIC_KEY );
|
||||
|
||||
// Select the first allowed cipher method.
|
||||
$allowed_methods = array( 'aes-256-ctr', 'aes-256-cbc' );
|
||||
$methods = array_intersect( $allowed_methods, openssl_get_cipher_methods() );
|
||||
$method = array_shift( $methods );
|
||||
|
||||
$iv = '';
|
||||
if ( $public_key && $method && openssl_seal( $data, $encrypted_data, $env_key, array( $public_key ), $method, $iv ) ) {
|
||||
// We are returning base64-encoded values to ensure they're characters we can use in JSON responses without issue.
|
||||
$return = array(
|
||||
'data' => base64_encode( $encrypted_data ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
'key' => base64_encode( $env_key[0] ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
'iv' => base64_encode( $iv ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
'cipher' => strtoupper( $method ),
|
||||
);
|
||||
}
|
||||
|
||||
// openssl_free_key was deprecated as no longer needed in PHP 8.0+. Can remove when PHP 8.0 is our minimum. (lol).
|
||||
if ( PHP_VERSION_ID < 80000 ) {
|
||||
openssl_free_key( $public_key ); // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.openssl_free_keyDeprecated, Generic.PHP.DeprecatedFunctions.Deprecated
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,815 @@
|
||||
<?php
|
||||
/**
|
||||
* Collection of tests to run on the Jetpack connection locally.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
||||
use Automattic\Jetpack\Connection\Tokens;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Sync\Health as Sync_Health;
|
||||
use Automattic\Jetpack\Sync\Settings as Sync_Settings;
|
||||
|
||||
/**
|
||||
* Class Jetpack_Cxn_Tests contains all of the actual tests.
|
||||
*/
|
||||
class Jetpack_Cxn_Tests extends Jetpack_Cxn_Test_Base {
|
||||
|
||||
/**
|
||||
* Jetpack_Cxn_Tests constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$methods = get_class_methods( 'Jetpack_Cxn_Tests' );
|
||||
|
||||
foreach ( $methods as $method ) {
|
||||
if ( ! str_contains( $method, 'test__' ) ) {
|
||||
continue;
|
||||
}
|
||||
$this->add_test( array( $this, $method ), $method, 'direct' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after loading default Jetpack Connection tests.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @since 8.3.0 Passes the Jetpack_Cxn_Tests instance.
|
||||
*/
|
||||
do_action( 'jetpack_connection_tests_loaded', $this );
|
||||
|
||||
/**
|
||||
* Determines if the WP.com testing suite should be included.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @since 8.1.0 Default false.
|
||||
*
|
||||
* @param bool $run_test To run the WP.com testing suite. Default false.
|
||||
*/
|
||||
if ( apply_filters( 'jetpack_debugger_run_self_test', false ) ) {
|
||||
/**
|
||||
* Intentionally added last as it checks for an existing failure state before attempting.
|
||||
* Generally, any failed location condition would result in the WP.com check to fail too, so
|
||||
* we will skip it to avoid confusing error messages.
|
||||
*
|
||||
* Note: This really should be an 'async' test.
|
||||
*/
|
||||
$this->add_test( array( $this, 'last__wpcom_self_test' ), 'test__wpcom_self_test', 'direct' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to look up the expected master user and return the local WP_User.
|
||||
*
|
||||
* @return WP_User Jetpack's expected master user.
|
||||
*/
|
||||
protected function helper_retrieve_local_master_user() {
|
||||
$master_user = Jetpack_Options::get_option( 'master_user' );
|
||||
return new WP_User( $master_user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Jetpack even connected and supposed to be talking to WP.com?
|
||||
*/
|
||||
protected function helper_is_jetpack_connected() {
|
||||
return Jetpack::is_connection_ready() && ! ( new Status() )->is_offline_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the `blog_token` if it exists.
|
||||
*
|
||||
* @return object|false
|
||||
*/
|
||||
protected function helper_get_blog_token() {
|
||||
return ( new Tokens() )->get_access_token();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a support url based on using a development version.
|
||||
*/
|
||||
protected function helper_get_support_url() {
|
||||
return Jetpack::is_development_version()
|
||||
? Redirect::get_url( 'jetpack-contact-support-beta-group' )
|
||||
: Redirect::get_url( 'jetpack-contact-support' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to reconnect Jetpack.
|
||||
*
|
||||
* @return string The reconnect url.
|
||||
*/
|
||||
protected static function helper_get_reconnect_url() {
|
||||
return admin_url( 'admin.php?page=jetpack#/reconnect' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets translated support text.
|
||||
*/
|
||||
protected function helper_get_support_text() {
|
||||
return __( 'Please contact Jetpack support.', 'jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translated text to reconnect Jetpack.
|
||||
*
|
||||
* @return string The translated reconnect text.
|
||||
*/
|
||||
protected static function helper_get_reconnect_text() {
|
||||
return __( 'Reconnect Jetpack now', 'jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translated text for failing tests due to timeouts.
|
||||
*
|
||||
* @return string The translated timeout text.
|
||||
*/
|
||||
protected static function helper_get_timeout_text() {
|
||||
return __( 'The test timed out which may sometimes indicate a failure or may be a false failure. Please relaunch tests.', 'jetpack' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets translated reconnect long description.
|
||||
*
|
||||
* @param string $connection_error The connection specific error.
|
||||
* @param string $recommendation The recommendation for resolving the connection error.
|
||||
*
|
||||
* @return string The translated long description for reconnection recommendations.
|
||||
*/
|
||||
protected static function helper_get_reconnect_long_description( $connection_error, $recommendation ) {
|
||||
|
||||
return sprintf(
|
||||
'<p>%1$s</p>' .
|
||||
'<p><span class="dashicons fail"><span class="screen-reader-text">%2$s</span></span> %3$s</p><p><strong>%4$s</strong></p>',
|
||||
__( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
|
||||
/* translators: screen reader text indicating a test failed */
|
||||
__( 'Error', 'jetpack' ),
|
||||
$connection_error,
|
||||
$recommendation
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return consistent responses for a connection failing test.
|
||||
*
|
||||
* @param string $name The raw method name that runs the test. Default unnamed_test.
|
||||
* @param string $connection_error The connection specific error. Default 'Your site is not connected to Jetpack.'.
|
||||
* @param string $recommendation The recommendation for resolving the connection error. Default 'We recommend reconnecting Jetpack.'.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
public static function connection_failing_test( $name, $connection_error = '', $recommendation = '' ) {
|
||||
$connection_error = empty( $connection_error ) ? __( 'Your site is not connected to Jetpack.', 'jetpack' ) : $connection_error;
|
||||
$recommendation = empty( $recommendation ) ? __( 'We recommend reconnecting Jetpack.', 'jetpack' ) : $recommendation;
|
||||
|
||||
$args = array(
|
||||
'name' => $name,
|
||||
'short_description' => $connection_error,
|
||||
'action' => self::helper_get_reconnect_url(),
|
||||
'action_label' => self::helper_get_reconnect_text(),
|
||||
'long_description' => self::helper_get_reconnect_long_description( $connection_error, $recommendation ),
|
||||
);
|
||||
|
||||
return self::failing_test( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets translated text to enable outbound requests.
|
||||
*
|
||||
* @param string $protocol Either 'HTTP' or 'HTTPS'.
|
||||
*
|
||||
* @return string The translated text.
|
||||
*/
|
||||
protected function helper_enable_outbound_requests( $protocol ) {
|
||||
return sprintf(
|
||||
/* translators: %1$s - request protocol, either http or https */
|
||||
__(
|
||||
'Your server did not successfully connect to the Jetpack server using %1$s
|
||||
Please ask your hosting provider to confirm your server can make outbound requests to jetpack.com.',
|
||||
'jetpack'
|
||||
),
|
||||
$protocol
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 30 for use with a filter.
|
||||
*
|
||||
* To allow time for WP.com to run upstream testing, this function exists to increase the http_request_timeout value
|
||||
* to 30.
|
||||
*
|
||||
* @return int 30
|
||||
*/
|
||||
public static function increase_timeout() {
|
||||
return 30; // seconds.
|
||||
}
|
||||
|
||||
/**
|
||||
* The test verifies the blog token exists.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function test__blog_token_if_exists() {
|
||||
$name = __FUNCTION__;
|
||||
|
||||
if ( ! $this->helper_is_jetpack_connected() ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Jetpack is not connected. No blog token to check.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
$blog_token = $this->helper_get_blog_token();
|
||||
|
||||
if ( $blog_token ) {
|
||||
$result = self::passing_test( array( 'name' => $name ) );
|
||||
} else {
|
||||
$connection_error = __( 'Blog token is missing.', 'jetpack' );
|
||||
|
||||
$result = self::connection_failing_test( $name, $connection_error );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if Jetpack is connected.
|
||||
*/
|
||||
protected function test__check_if_connected() {
|
||||
$name = __FUNCTION__;
|
||||
|
||||
if ( ! $this->helper_get_blog_token() ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Blog token is missing.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->helper_is_jetpack_connected() ) {
|
||||
$result = self::passing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'label' => __( 'Your site is connected to Jetpack', 'jetpack' ),
|
||||
'long_description' => sprintf(
|
||||
'<p>%1$s</p>' .
|
||||
'<p><span class="dashicons pass"><span class="screen-reader-text">%2$s</span></span> %3$s</p>',
|
||||
__( 'A healthy connection ensures Jetpack essential services are provided to your WordPress site, such as Stats and Site Security.', 'jetpack' ),
|
||||
/* translators: Screen reader text indicating a test has passed */
|
||||
__( 'Passed', 'jetpack' ),
|
||||
__( 'Your site is connected to Jetpack.', 'jetpack' )
|
||||
),
|
||||
)
|
||||
);
|
||||
} elseif ( ( new Status() )->is_offline_mode() ) {
|
||||
$result = self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Jetpack is in Offline Mode:', 'jetpack' ) . ' ' . Jetpack::development_mode_trigger_text(),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$connection_error = __( 'Your site is not connected to Jetpack', 'jetpack' );
|
||||
|
||||
$result = self::connection_failing_test( $name, $connection_error );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the master user still exists on this site.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function test__master_user_exists_on_site() {
|
||||
$name = __FUNCTION__;
|
||||
if ( ! $this->helper_is_jetpack_connected() ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Jetpack is not connected. No master user to check.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( ! ( new Connection_Manager() )->get_connection_owner_id() ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Jetpack is running without a connected user. No master user to check.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
$local_user = $this->helper_retrieve_local_master_user();
|
||||
|
||||
if ( $local_user->exists() ) {
|
||||
$result = self::passing_test( array( 'name' => $name ) );
|
||||
} else {
|
||||
$connection_error = __( 'The user who setup the Jetpack connection no longer exists on this site.', 'jetpack' );
|
||||
|
||||
$result = self::connection_failing_test( $name, $connection_error );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the master user has the manage options capability (e.g. is an admin).
|
||||
*
|
||||
* Generic calls from WP.com execute on Jetpack as the master user. If it isn't an admin, random things will fail.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function test__master_user_can_manage_options() {
|
||||
$name = __FUNCTION__;
|
||||
if ( ! $this->helper_is_jetpack_connected() ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( ! ( new Connection_Manager() )->get_connection_owner_id() ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Jetpack is running without a connected user. No master user to check.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
$master_user = $this->helper_retrieve_local_master_user();
|
||||
|
||||
if ( user_can( $master_user, 'manage_options' ) ) {
|
||||
$result = self::passing_test( array( 'name' => $name ) );
|
||||
} else {
|
||||
/* translators: a WordPress username */
|
||||
$connection_error = sprintf( __( 'The user (%s) who setup the Jetpack connection is not an administrator.', 'jetpack' ), $master_user->user_login );
|
||||
/* translators: a WordPress username */
|
||||
$recommendation = sprintf( __( 'We recommend either upgrading the user (%s) or reconnecting Jetpack.', 'jetpack' ), $master_user->user_login );
|
||||
|
||||
$result = self::connection_failing_test( $name, $connection_error, $recommendation );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the PHP's XML library is installed.
|
||||
*
|
||||
* While it should be installed by default, increasingly in PHP 7, some OSes require an additional php-xml package.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function test__xml_parser_available() {
|
||||
$name = __FUNCTION__;
|
||||
if ( function_exists( 'xml_parser_create' ) ) {
|
||||
$result = self::passing_test( array( 'name' => $name ) );
|
||||
} else {
|
||||
$result = self::failing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'label' => __( 'PHP XML manipulation libraries are not available.', 'jetpack' ),
|
||||
'short_description' => __( 'Please ask your hosting provider to refer to our server requirements and enable PHP\'s XML module.', 'jetpack' ),
|
||||
'action_label' => __( 'View our server requirements', 'jetpack' ),
|
||||
'action' => Redirect::get_url( 'jetpack-support-server-requirements' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the server is able to send an outbound http communication.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function test__outbound_http() {
|
||||
$name = __FUNCTION__;
|
||||
$request = wp_remote_get( preg_replace( '/^https:/', 'http:', JETPACK__API_BASE ) . 'test/1/' );
|
||||
$code = wp_remote_retrieve_response_code( $request );
|
||||
|
||||
if ( 200 === (int) $code ) {
|
||||
$result = self::passing_test( array( 'name' => $name ) );
|
||||
} else {
|
||||
$result = self::failing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => $this->helper_enable_outbound_requests( 'HTTP' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the server is able to send an outbound https communication.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function test__outbound_https() {
|
||||
$name = __FUNCTION__;
|
||||
$request = wp_remote_get( preg_replace( '/^http:/', 'https:', JETPACK__API_BASE ) . 'test/1/' );
|
||||
$code = wp_remote_retrieve_response_code( $request );
|
||||
|
||||
if ( 200 === (int) $code ) {
|
||||
$result = self::passing_test( array( 'name' => $name ) );
|
||||
} else {
|
||||
$result = self::failing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => $this->helper_enable_outbound_requests( 'HTTPS' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for an IDC.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function test__identity_crisis() {
|
||||
$name = __FUNCTION__;
|
||||
if ( ! $this->helper_is_jetpack_connected() ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Jetpack is not connected.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
$identity_crisis = Jetpack::check_identity_crisis();
|
||||
|
||||
if ( ! $identity_crisis ) {
|
||||
$result = self::passing_test( array( 'name' => $name ) );
|
||||
} else {
|
||||
$result = self::failing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => sprintf(
|
||||
/* translators: Two URLs. The first is the locally-recorded value, the second is the value as recorded on WP.com. */
|
||||
__( 'Your url is set as `%1$s`, but your WordPress.com connection lists it as `%2$s`!', 'jetpack' ),
|
||||
$identity_crisis['home'],
|
||||
$identity_crisis['wpcom_home']
|
||||
),
|
||||
'action_label' => $this->helper_get_support_text(),
|
||||
'action' => $this->helper_get_support_url(),
|
||||
)
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the health of the Connection tokens.
|
||||
*
|
||||
* This will always check the blog token health. It will also check the user token health if
|
||||
* a user is logged in and connected, or if there's a connected owner.
|
||||
*
|
||||
* @since 9.0.0
|
||||
* @since 9.6.0 Checks only blog token if current user not connected or site does not have a connected owner.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function test__connection_token_health() {
|
||||
$name = __FUNCTION__;
|
||||
$m = new Connection_Manager();
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
// Check if there's a connected logged in user.
|
||||
if ( $user_id && ! $m->is_user_connected( $user_id ) ) {
|
||||
$user_id = false;
|
||||
}
|
||||
|
||||
// If no logged in user to check, let's see if there's a master_user set.
|
||||
if ( ! $user_id ) {
|
||||
$user_id = Jetpack_Options::get_option( 'master_user' );
|
||||
if ( $user_id && ! $m->is_user_connected( $user_id ) ) {
|
||||
return self::connection_failing_test( $name, __( 'Missing token for the connection owner.', 'jetpack' ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $user_id ) {
|
||||
return $this->check_tokens_health( $user_id );
|
||||
} else {
|
||||
return $this->check_blog_token_health();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests blog and user's token against wp.com's check-token-health endpoint.
|
||||
*
|
||||
* @since 9.6.0
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function check_blog_token_health() {
|
||||
$name = 'test__connection_token_health';
|
||||
$valid = ( new Tokens() )->validate_blog_token();
|
||||
|
||||
if ( ! $valid ) {
|
||||
return self::connection_failing_test( $name, __( 'Blog token validation failed.', 'jetpack' ) );
|
||||
} else {
|
||||
return self::passing_test( array( 'name' => $name ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests blog token against wp.com's check-token-health endpoint.
|
||||
*
|
||||
* @since 9.6.0
|
||||
*
|
||||
* @param int $user_id The user ID to check the tokens for.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function check_tokens_health( $user_id ) {
|
||||
$name = 'test__connection_token_health';
|
||||
$validated_tokens = ( new Tokens() )->validate( $user_id );
|
||||
|
||||
if ( ! is_array( $validated_tokens ) || count( array_diff_key( array_flip( array( 'blog_token', 'user_token' ) ), $validated_tokens ) ) ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Token health check failed to validate tokens.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$invalid_tokens_exist = false;
|
||||
foreach ( $validated_tokens as $validated_token ) {
|
||||
if ( ! $validated_token['is_healthy'] ) {
|
||||
$invalid_tokens_exist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( false === $invalid_tokens_exist ) {
|
||||
return self::passing_test( array( 'name' => $name ) );
|
||||
}
|
||||
|
||||
$connection_error = __( 'Invalid Jetpack connection tokens.', 'jetpack' );
|
||||
|
||||
return self::connection_failing_test( $name, $connection_error );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests connection status against wp.com's test-connection endpoint.
|
||||
*
|
||||
* @todo: Compare with the wpcom_self_test. We only need one of these.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function test__wpcom_connection_test() {
|
||||
$name = __FUNCTION__;
|
||||
|
||||
$status = new Status();
|
||||
if ( ! Jetpack::is_connection_ready() || $status->is_offline_mode() || $status->in_safe_mode() || ! $this->pass ) {
|
||||
return self::skipped_test( array( 'name' => $name ) );
|
||||
}
|
||||
|
||||
add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
|
||||
Client::WPCOM_JSON_API_VERSION
|
||||
);
|
||||
remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ) );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
if ( str_contains( $response->get_error_message(), 'cURL error 28' ) ) { // Timeout.
|
||||
$result = self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => self::helper_get_timeout_text(),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
/* translators: %1$s is the error code, %2$s is the error message */
|
||||
$message = sprintf( __( 'Connection test failed (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() );
|
||||
|
||||
$result = self::connection_failing_test( $name, $message );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
if ( ! $body ) {
|
||||
return self::failing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'Connection test failed (empty response body)', 'jetpack' ) . wp_remote_retrieve_response_code( $response ),
|
||||
'action_label' => $this->helper_get_support_text(),
|
||||
'action' => $this->helper_get_support_url(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( 404 === wp_remote_retrieve_response_code( $response ) ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'The WordPress.com API returned a 404 error.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$result = json_decode( $body );
|
||||
$is_connected = ! empty( $result->connected );
|
||||
$message = $result->message . ': ' . wp_remote_retrieve_response_code( $response );
|
||||
|
||||
if ( $is_connected ) {
|
||||
$res = self::passing_test( array( 'name' => $name ) );
|
||||
} else {
|
||||
$res = self::connection_failing_test( $name, $message );
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the port number to ensure it is an expected value.
|
||||
*
|
||||
* We expect that sites on be on one of:
|
||||
* port 80,
|
||||
* port 443 (https sites only),
|
||||
* the value of JETPACK_SIGNATURE__HTTP_PORT,
|
||||
* unless the site is intentionally on a different port (e.g. example.com:8080 is the site's URL).
|
||||
*
|
||||
* If the value isn't one of those and the site's URL doesn't include a port, then the signature verification will fail.
|
||||
*
|
||||
* This happens most commonly on sites with reverse proxies, so the edge (e.g. Varnish) is running on 80/443, but nginx
|
||||
* or Apache is responding internally on a different port (e.g. 81).
|
||||
*
|
||||
* @return array Test results
|
||||
*/
|
||||
protected function test__server_port_value() {
|
||||
$name = __FUNCTION__;
|
||||
if ( ! isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) && ! isset( $_SERVER['SERVER_PORT'] ) ) {
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => __( 'The server port values are not defined. This is most common when running PHP via a CLI.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
$site_port = wp_parse_url( home_url(), PHP_URL_PORT );
|
||||
$server_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? (int) $_SERVER['HTTP_X_FORWARDED_PORT'] : (int) $_SERVER['SERVER_PORT'];
|
||||
$http_ports = array( 80 );
|
||||
$https_ports = array( 80, 443 );
|
||||
|
||||
if ( defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ) {
|
||||
$http_ports[] = JETPACK_SIGNATURE__HTTP_PORT;
|
||||
}
|
||||
|
||||
if ( defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ) {
|
||||
$https_ports[] = JETPACK_SIGNATURE__HTTPS_PORT;
|
||||
}
|
||||
|
||||
if ( $site_port ) {
|
||||
return self::skipped_test( array( 'name' => $name ) ); // Not currently testing for this situation.
|
||||
}
|
||||
|
||||
if ( is_ssl() && in_array( $server_port, $https_ports, true ) ) {
|
||||
return self::passing_test( array( 'name' => $name ) );
|
||||
} elseif ( in_array( $server_port, $http_ports, true ) ) {
|
||||
return self::passing_test( array( 'name' => $name ) );
|
||||
} else {
|
||||
if ( is_ssl() ) {
|
||||
$needed_constant = 'JETPACK_SIGNATURE__HTTPS_PORT';
|
||||
} else {
|
||||
$needed_constant = 'JETPACK_SIGNATURE__HTTP_PORT';
|
||||
}
|
||||
return self::failing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => sprintf(
|
||||
/* translators: %1$s - a PHP code snippet */
|
||||
__(
|
||||
'The server port value is unexpected.
|
||||
Try adding the following to your wp-config.php file: %1$s',
|
||||
'jetpack'
|
||||
),
|
||||
"define( '$needed_constant', $server_port )"
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync Health Tests.
|
||||
*
|
||||
* Disabled: Results in a failing test (recommended)
|
||||
* Delayed: Results in failing test (recommended)
|
||||
* Error: Results in failing test (critical)
|
||||
*/
|
||||
protected function test__sync_health() {
|
||||
|
||||
$name = __FUNCTION__;
|
||||
|
||||
if ( ! $this->helper_is_jetpack_connected() ) {
|
||||
// If the site is not connected, there is no point in testing Sync health.
|
||||
return self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'show_in_site_health' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Sync is disabled.
|
||||
if ( ! Sync_Settings::is_sync_enabled() ) {
|
||||
return self::failing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'label' => __( 'Jetpack Sync has been disabled on your site.', 'jetpack' ),
|
||||
'severity' => 'recommended',
|
||||
'action' => 'https://github.com/Automattic/jetpack/blob/trunk/projects/packages/sync/src/class-settings.php',
|
||||
'action_label' => __( 'See Github for more on Sync Settings', 'jetpack' ),
|
||||
'short_description' => __( 'Jetpack Sync has been disabled on your site. This could be impacting some of your site’s Jetpack-powered features. Developers may enable / disable syncing using the Sync Settings API.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
// Sync has experienced Data Loss.
|
||||
if ( Sync_Health::get_status() === Sync_Health::STATUS_OUT_OF_SYNC ) {
|
||||
return self::failing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'label' => __( 'Jetpack has detected a problem with the communication between your site and WordPress.com', 'jetpack' ),
|
||||
'severity' => 'critical',
|
||||
'action' => Redirect::get_url( 'jetpack-contact-support' ),
|
||||
'action_label' => __( 'Contact Jetpack Support', 'jetpack' ),
|
||||
'short_description' => __( 'There is a problem with the communication between your site and WordPress.com. This could be impacting some of your site’s Jetpack-powered features. If you continue to see this error, please contact support for assistance.', 'jetpack' ),
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return self::passing_test( array( 'name' => $name ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls to WP.com to run the connection diagnostic testing suite.
|
||||
*
|
||||
* Intentionally added last as it will be skipped if any local failed conditions exist.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @since 7.9.0 Timeout waiting for a WP.com response no longer fails the test. Test is marked skipped instead.
|
||||
*
|
||||
* @return array Test results.
|
||||
*/
|
||||
protected function last__wpcom_self_test() {
|
||||
$name = 'test__wpcom_self_test';
|
||||
|
||||
$status = new Status();
|
||||
if ( ! Jetpack::is_connection_ready() || $status->is_offline_mode() || $status->in_safe_mode() || ! $this->pass ) {
|
||||
return self::skipped_test( array( 'name' => $name ) );
|
||||
}
|
||||
|
||||
$self_xml_rpc_url = site_url( 'xmlrpc.php' );
|
||||
|
||||
$testsite_url = JETPACK__API_BASE . 'testsite/1/?url=';
|
||||
|
||||
// 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.
|
||||
add_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ), PHP_INT_MAX - 1 );
|
||||
|
||||
$response = wp_remote_get( $testsite_url . $self_xml_rpc_url );
|
||||
|
||||
remove_filter( 'http_request_timeout', array( 'Jetpack_Cxn_Tests', 'increase_timeout' ), PHP_INT_MAX - 1 );
|
||||
|
||||
if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
|
||||
$result = self::passing_test( array( 'name' => $name ) );
|
||||
} elseif ( is_wp_error( $response ) && str_contains( $response->get_error_message(), 'cURL error 28' ) ) { // Timeout.
|
||||
$result = self::skipped_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => self::helper_get_timeout_text(),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$result = self::failing_test(
|
||||
array(
|
||||
'name' => $name,
|
||||
'short_description' => sprintf(
|
||||
/* translators: %1$s - A debugging url */
|
||||
__( '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' ),
|
||||
Redirect::get_url( 'jetpack-support-debug', array( 'query' => 'url=' . rawurlencode( site_url() ) ) )
|
||||
),
|
||||
'action_label' => $this->helper_get_support_text(),
|
||||
'action' => $this->helper_get_support_url(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack Debug Data for the Site Health sections.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Connection\Tokens;
|
||||
use Automattic\Jetpack\Connection\Urls;
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
|
||||
use Automattic\Jetpack\Identity_Crisis;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Sync\Modules;
|
||||
use Automattic\Jetpack\Sync\Sender;
|
||||
|
||||
/**
|
||||
* Class Jetpack_Debug_Data
|
||||
*
|
||||
* Collect and return debug data for Jetpack.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*/
|
||||
class Jetpack_Debug_Data {
|
||||
/**
|
||||
* Determine the active plan and normalize it for the debugger results.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*
|
||||
* @return string The plan slug.
|
||||
*/
|
||||
public static function what_jetpack_plan() {
|
||||
$plan = Jetpack_Plan::get();
|
||||
return ! empty( $plan['class'] ) ? $plan['class'] : 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert seconds to human readable time.
|
||||
*
|
||||
* A dedication function instead of using Core functionality to allow for output in seconds.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*
|
||||
* @param int $seconds Number of seconds to convert to human time.
|
||||
*
|
||||
* @return string Human readable time.
|
||||
*/
|
||||
public static function seconds_to_time( $seconds ) {
|
||||
$seconds = (int) $seconds;
|
||||
$units = array(
|
||||
'week' => WEEK_IN_SECONDS,
|
||||
'day' => DAY_IN_SECONDS,
|
||||
'hour' => HOUR_IN_SECONDS,
|
||||
'minute' => MINUTE_IN_SECONDS,
|
||||
'second' => 1,
|
||||
);
|
||||
// specifically handle zero.
|
||||
if ( 0 === $seconds ) {
|
||||
return '0 seconds';
|
||||
}
|
||||
$human_readable = '';
|
||||
foreach ( $units as $name => $divisor ) {
|
||||
$quot = (int) ( $seconds / $divisor );
|
||||
if ( $quot ) {
|
||||
$human_readable .= "$quot $name";
|
||||
$human_readable .= ( abs( $quot ) > 1 ? 's' : '' ) . ', ';
|
||||
$seconds -= $quot * $divisor;
|
||||
}
|
||||
}
|
||||
return substr( $human_readable, 0, -2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return debug data in the format expected by Core's Site Health Info tab.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*
|
||||
* @param array $debug {
|
||||
* The debug information already compiled by Core.
|
||||
*
|
||||
* @type string $label The title for this section of the debug output.
|
||||
* @type string $description Optional. A description for your information section which may contain basic HTML
|
||||
* markup: `em`, `strong` and `a` for linking to documentation or putting emphasis.
|
||||
* @type boolean $show_count Optional. If set to `true` the amount of fields will be included in the title for
|
||||
* this section.
|
||||
* @type boolean $private Optional. If set to `true` the section and all associated fields will be excluded
|
||||
* from the copy-paste text area.
|
||||
* @type array $fields {
|
||||
* An associative array containing the data to be displayed.
|
||||
*
|
||||
* @type string $label The label for this piece of information.
|
||||
* @type string $value The output that is of interest for this field.
|
||||
* @type boolean $private Optional. If set to `true` the field will not be included in the copy-paste text area
|
||||
* on top of the page, allowing you to show, for example, API keys here.
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @return array $args Debug information in the same format as the initial argument.
|
||||
*/
|
||||
public static function core_debug_data( $debug ) {
|
||||
$support_url = Jetpack::is_development_version()
|
||||
? Redirect::get_url( 'jetpack-contact-support-beta-group' )
|
||||
: Redirect::get_url( 'jetpack-contact-support' );
|
||||
|
||||
$jetpack = array(
|
||||
'jetpack' => array(
|
||||
'label' => __( 'Jetpack', 'jetpack' ),
|
||||
'description' => sprintf(
|
||||
/* translators: %1$s is URL to jetpack.com's contact support page. %2$s accessibility text */
|
||||
__(
|
||||
'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>',
|
||||
'jetpack'
|
||||
),
|
||||
esc_url( $support_url ),
|
||||
__( '(opens in a new tab)', 'jetpack' )
|
||||
),
|
||||
'fields' => self::debug_data(),
|
||||
),
|
||||
);
|
||||
$debug = array_merge( $debug, $jetpack );
|
||||
return $debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile and return array of debug information.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*
|
||||
* @return array $args {
|
||||
* Associated array of arrays with the following.
|
||||
* @type string $label The label for this piece of information.
|
||||
* @type string $value The output that is of interest for this field.
|
||||
* @type boolean $private Optional. Set to true if data is sensitive (API keys, etc).
|
||||
* }
|
||||
*/
|
||||
public static function debug_data() {
|
||||
$debug_info = array();
|
||||
|
||||
/* Add various important Jetpack options */
|
||||
$debug_info['site_id'] = array(
|
||||
'label' => 'Jetpack Site ID',
|
||||
'value' => Jetpack_Options::get_option( 'id' ),
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['ssl_cert'] = array(
|
||||
'label' => 'Jetpack SSL Verfication Bypass',
|
||||
'value' => ( Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) ? 'Yes' : 'No',
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['time_diff'] = array(
|
||||
'label' => "Offset between Jetpack server's time and this server's time.",
|
||||
'value' => Jetpack_Options::get_option( 'time_diff' ),
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['version_option'] = array(
|
||||
'label' => 'Current Jetpack Version Option',
|
||||
'value' => Jetpack_Options::get_option( 'version' ),
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['old_version'] = array(
|
||||
'label' => 'Previous Jetpack Version',
|
||||
'value' => Jetpack_Options::get_option( 'old_version' ),
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['public'] = array(
|
||||
'label' => 'Jetpack Site Public',
|
||||
'value' => ( Jetpack_Options::get_option( 'public' ) ) ? 'Public' : 'Private',
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['master_user'] = array(
|
||||
'label' => 'Jetpack Master User',
|
||||
'value' => self::human_readable_master_user(), // Only ID number and user name.
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['is_offline_mode'] = array(
|
||||
'label' => 'Jetpack Offline Mode',
|
||||
'value' => ( new Status() )->is_offline_mode() ? 'on' : 'off',
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['is_offline_mode_constant'] = array(
|
||||
'label' => 'JETPACK_DEV_DEBUG Constant',
|
||||
'value' => ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) ? 'on' : 'off',
|
||||
'private' => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Token information is private, but awareness if there one is set is helpful.
|
||||
*
|
||||
* To balance out information vs privacy, we only display and include the "key",
|
||||
* which is a segment of the token prior to a period within the token and is
|
||||
* technically not private.
|
||||
*
|
||||
* If a token does not contain a period, then it is malformed and we report it as such.
|
||||
*/
|
||||
$user_id = get_current_user_id();
|
||||
$blog_token = ( new Tokens() )->get_access_token();
|
||||
$user_token = ( new Tokens() )->get_access_token( $user_id );
|
||||
|
||||
$tokenset = '';
|
||||
if ( $blog_token ) {
|
||||
$tokenset = 'Blog ';
|
||||
$blog_key = substr( $blog_token->secret, 0, strpos( $blog_token->secret, '.' ) );
|
||||
// Intentionally not translated since this is helpful when sent to Happiness.
|
||||
$blog_key = ( $blog_key ) ? $blog_key : 'Potentially Malformed Token.';
|
||||
}
|
||||
if ( $user_token ) {
|
||||
$tokenset .= 'User';
|
||||
$user_key = substr( $user_token->secret, 0, strpos( $user_token->secret, '.' ) );
|
||||
// Intentionally not translated since this is helpful when sent to Happiness.
|
||||
$user_key = ( $user_key ) ? $user_key : 'Potentially Malformed Token.';
|
||||
}
|
||||
if ( ! $tokenset ) {
|
||||
$tokenset = 'None';
|
||||
}
|
||||
|
||||
$debug_info['current_user'] = array(
|
||||
'label' => 'Current User',
|
||||
'value' => self::human_readable_user( $user_id ),
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['tokens_set'] = array(
|
||||
'label' => 'Tokens defined',
|
||||
'value' => $tokenset,
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['blog_token'] = array(
|
||||
'label' => 'Blog Public Key',
|
||||
'value' => ( $blog_token ) ? $blog_key : 'Not set.',
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['user_token'] = array(
|
||||
'label' => 'User Public Key',
|
||||
'value' => ( $user_token ) ? $user_key : 'Not set.',
|
||||
'private' => false,
|
||||
);
|
||||
|
||||
/** Jetpack Environmental Information */
|
||||
$debug_info['version'] = array(
|
||||
'label' => 'Jetpack Version',
|
||||
'value' => JETPACK__VERSION,
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['jp_plugin_dir'] = array(
|
||||
'label' => 'Jetpack Directory',
|
||||
'value' => JETPACK__PLUGIN_DIR,
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['plan'] = array(
|
||||
'label' => 'Plan Type',
|
||||
'value' => self::what_jetpack_plan(),
|
||||
'private' => false,
|
||||
);
|
||||
|
||||
foreach ( array(
|
||||
'HTTP_HOST',
|
||||
'SERVER_PORT',
|
||||
'HTTPS',
|
||||
'GD_PHP_HANDLER',
|
||||
'HTTP_AKAMAI_ORIGIN_HOP',
|
||||
'HTTP_CF_CONNECTING_IP',
|
||||
'HTTP_CLIENT_IP',
|
||||
'HTTP_FASTLY_CLIENT_IP',
|
||||
'HTTP_FORWARDED',
|
||||
'HTTP_FORWARDED_FOR',
|
||||
'HTTP_INCAP_CLIENT_IP',
|
||||
'HTTP_TRUE_CLIENT_IP',
|
||||
'HTTP_X_CLIENTIP',
|
||||
'HTTP_X_CLUSTER_CLIENT_IP',
|
||||
'HTTP_X_FORWARDED',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_IP_TRAIL',
|
||||
'HTTP_X_REAL_IP',
|
||||
'HTTP_X_VARNISH',
|
||||
'REMOTE_ADDR',
|
||||
) as $header ) {
|
||||
if ( isset( $_SERVER[ $header ] ) ) {
|
||||
$debug_info[ $header ] = array(
|
||||
'label' => 'Server Variable ' . $header,
|
||||
'value' => empty( $_SERVER[ $header ] ) ? 'false' : filter_var( wp_unslash( $_SERVER[ $header ] ) ),
|
||||
'private' => true, // This isn't really 'private' information, but we don't want folks to easily paste these into public forums.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$debug_info['protect_header'] = array(
|
||||
'label' => 'Trusted IP',
|
||||
'value' => wp_json_encode( get_site_option( 'trusted_ip_header' ) ),
|
||||
'private' => false,
|
||||
);
|
||||
|
||||
/** Sync Debug Information */
|
||||
$sync_module = Modules::get_module( 'full-sync' );
|
||||
'@phan-var \Automattic\Jetpack\Sync\Modules\Full_Sync_Immediately|\Automattic\Jetpack\Sync\Modules\Full_Sync $sync_module';
|
||||
if ( $sync_module ) {
|
||||
$sync_statuses = $sync_module->get_status();
|
||||
$human_readable_sync_status = array();
|
||||
foreach ( $sync_statuses as $sync_status => $sync_status_value ) {
|
||||
$human_readable_sync_status[ $sync_status ] =
|
||||
in_array( $sync_status, array( 'started', 'queue_finished', 'send_started', 'finished' ), true )
|
||||
? gmdate( 'r', $sync_status_value ) : $sync_status_value;
|
||||
}
|
||||
$debug_info['full_sync'] = array(
|
||||
'label' => 'Full Sync Status',
|
||||
'value' => wp_json_encode( $human_readable_sync_status ),
|
||||
'private' => false,
|
||||
);
|
||||
}
|
||||
|
||||
$queue = Sender::get_instance()->get_sync_queue();
|
||||
|
||||
$debug_info['sync_size'] = array(
|
||||
'label' => 'Sync Queue Size',
|
||||
'value' => $queue->size(),
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['sync_lag'] = array(
|
||||
'label' => 'Sync Queue Lag',
|
||||
'value' => self::seconds_to_time( $queue->lag() ),
|
||||
'private' => false,
|
||||
);
|
||||
|
||||
$full_sync_queue = Sender::get_instance()->get_full_sync_queue();
|
||||
|
||||
$debug_info['full_sync_size'] = array(
|
||||
'label' => 'Full Sync Queue Size',
|
||||
'value' => $full_sync_queue->size(),
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['full_sync_lag'] = array(
|
||||
'label' => 'Full Sync Queue Lag',
|
||||
'value' => self::seconds_to_time( $full_sync_queue->lag() ),
|
||||
'private' => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* IDC Information
|
||||
*
|
||||
* Must follow sync debug since it depends on sync functionality.
|
||||
*/
|
||||
$idc_urls = array(
|
||||
'home' => Urls::home_url(),
|
||||
'siteurl' => Urls::site_url(),
|
||||
'WP_HOME' => Constants::is_defined( 'WP_HOME' ) ? Constants::get_constant( 'WP_HOME' ) : '',
|
||||
'WP_SITEURL' => Constants::is_defined( 'WP_SITEURL' ) ? Constants::get_constant( 'WP_SITEURL' ) : '',
|
||||
);
|
||||
|
||||
$debug_info['idc_urls'] = array(
|
||||
'label' => 'IDC URLs',
|
||||
'value' => wp_json_encode( $idc_urls ),
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['idc_error_option'] = array(
|
||||
'label' => 'IDC Error Option',
|
||||
'value' => wp_json_encode( Jetpack_Options::get_option( 'sync_error_idc' ) ),
|
||||
'private' => false,
|
||||
);
|
||||
$debug_info['idc_optin'] = array(
|
||||
'label' => 'IDC Opt-in',
|
||||
'value' => Identity_Crisis::should_handle_idc(),
|
||||
'private' => false,
|
||||
);
|
||||
|
||||
// @todo -- Add testing results?
|
||||
$cxn_tests = new Jetpack_Cxn_Tests();
|
||||
$debug_info['cxn_tests'] = array(
|
||||
'label' => 'Connection Tests',
|
||||
'value' => '',
|
||||
'private' => false,
|
||||
);
|
||||
if ( $cxn_tests->pass() ) {
|
||||
$debug_info['cxn_tests']['value'] = 'All Pass.';
|
||||
} else {
|
||||
$debug_info['cxn_tests']['value'] = wp_json_encode( $cxn_tests->list_fails() );
|
||||
}
|
||||
|
||||
return $debug_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human readable string for which user is the master user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function human_readable_master_user() {
|
||||
$master_user = Jetpack_Options::get_option( 'master_user' );
|
||||
|
||||
if ( ! $master_user ) {
|
||||
return __( 'No master user set.', 'jetpack' );
|
||||
}
|
||||
|
||||
$user = new WP_User( $master_user );
|
||||
|
||||
if ( ! $user ) {
|
||||
return __( 'Master user no longer exists. Please disconnect and reconnect Jetpack.', 'jetpack' );
|
||||
}
|
||||
|
||||
return self::human_readable_user( $user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return human readable string for a given user object.
|
||||
*
|
||||
* @param WP_User|int $user Object or ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function human_readable_user( $user ) {
|
||||
$user = new WP_User( $user );
|
||||
|
||||
return sprintf( '#%1$d %2$s', $user->ID, $user->user_login ); // Format: "#1 username".
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack Debugger functionality allowing for self-service diagnostic information via the legacy jetpack debugger.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Automattic\Jetpack\Status;
|
||||
|
||||
/**
|
||||
* Class Jetpack_Debugger
|
||||
*
|
||||
* A namespacing class for functionality related to the legacy in-plugin diagnostic tooling.
|
||||
*/
|
||||
class Jetpack_Debugger {
|
||||
/**
|
||||
* Disconnect Jetpack and redirect user to connection flow.
|
||||
*
|
||||
* Used in class.jetpack-admin.php.
|
||||
*/
|
||||
public static function disconnect_and_redirect() {
|
||||
if ( ! ( isset( $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], 'jp_disconnect' ) ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! empty( $_GET['disconnect'] ) ) {
|
||||
if ( Jetpack::is_connection_ready() ) {
|
||||
Jetpack::disconnect();
|
||||
wp_safe_redirect( Jetpack::admin_url() );
|
||||
exit( 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles output to the browser for the in-plugin debugger.
|
||||
*/
|
||||
public static function jetpack_debug_display_handler() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'jetpack' ) );
|
||||
}
|
||||
|
||||
$support_url = Jetpack::is_development_version()
|
||||
? Redirect::get_url( 'jetpack-contact-support-beta-group' )
|
||||
: Redirect::get_url( 'jetpack-contact-support' );
|
||||
|
||||
$cxntests = new Jetpack_Cxn_Tests();
|
||||
?>
|
||||
<div class="wrap">
|
||||
<div class="jp-static-block">
|
||||
<h2><?php esc_html_e( 'Debugging Center', 'jetpack' ); ?></h2>
|
||||
|
||||
<div class="jp-static-block-body">
|
||||
<h3><?php esc_html_e( "Testing your site's compatibility with Jetpack...", 'jetpack' ); ?></h3>
|
||||
<div class="jetpack-debug-test-container">
|
||||
<?php
|
||||
if ( $cxntests->pass() ) {
|
||||
echo '<div class="jetpack-tests-succeed">' . esc_html__( 'Your Jetpack setup looks a-okay!', 'jetpack' ) . '</div>';
|
||||
} else {
|
||||
$failures = $cxntests->list_fails();
|
||||
foreach ( $failures as $fail ) {
|
||||
?>
|
||||
<div class="notice notice-error inline">
|
||||
<div class="notice-icon-wrapper">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" width="24" height="24" class="y_IPyP1wIAOhyNaqvXJq" aria-hidden="true" focusable="false"><path d="M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm1.13 9.38l.35-6.46H8.52l.35 6.46h2.26zm-.09 3.36c.24-.23.37-.55.37-.96 0-.42-.12-.74-.36-.97s-.59-.35-1.06-.35-.82.12-1.07.35-.37.55-.37.97c0 .41.13.73.38.96.26.23.61.34 1.06.34s.8-.11 1.05-.34z"></path></svg>
|
||||
</div>
|
||||
|
||||
<div class="notice-main-content">
|
||||
<div class="notice-title"><?php echo esc_html( $fail['short_description'] ); ?></div>
|
||||
|
||||
<div class="notice-action-bar">
|
||||
<div>
|
||||
<a href="<?php echo esc_attr( $fail['action'] ); ?>" aria-disabled="false" class="components-button is-primary"><span><?php echo esc_html( $fail['action_label'] ); ?></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="entry-content">
|
||||
<h4><?php esc_html_e( 'Trouble with Jetpack?', 'jetpack' ); ?></h4>
|
||||
<p><?php esc_html_e( 'It may be caused by one of these issues, which you can diagnose yourself:', 'jetpack' ); ?></p>
|
||||
<ol>
|
||||
<li><?php esc_html_e( 'A known issue.', 'jetpack' ); ?>
|
||||
<?php
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: URLs to Jetpack support pages. */
|
||||
__( 'Some themes and plugins have <a href="%1$s" target="_blank" rel="noopener noreferrer">known conflicts</a> with Jetpack – check the list. (You can also browse the <a href="%2$s" target="_blank" rel="noopener noreferrer">Jetpack support pages</a> or <a href="%3$s" target="_blank" rel="noopener noreferrer">Jetpack support forum</a> to see if others have experienced and solved the problem.)', 'jetpack' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'target' => array(),
|
||||
'rel' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
esc_url( Redirect::get_url( 'jetpack-contact-support-known-issues' ) ),
|
||||
esc_url( Redirect::get_url( 'jetpack-support' ) ),
|
||||
esc_url( Redirect::get_url( 'wporg-support-plugin-jetpack' ) )
|
||||
);
|
||||
?>
|
||||
</li>
|
||||
<li>
|
||||
<?php esc_html_e( 'An incompatible plugin.', 'jetpack' ); ?>
|
||||
<?php esc_html_e( "Find out by disabling all plugins except Jetpack. If the problem persists, it's not a plugin issue. If the problem is solved, turn your plugins on one by one until the problem pops up again – there's the culprit! Let us know, and we'll try to help.", 'jetpack' ); ?>
|
||||
</li>
|
||||
<li>
|
||||
<?php esc_html_e( 'A theme conflict.', 'jetpack' ); ?>
|
||||
<?php
|
||||
$default_theme = wp_get_theme( WP_DEFAULT_THEME );
|
||||
|
||||
if ( $default_theme->exists() ) {
|
||||
/* translators: %s is the name of a theme */
|
||||
echo esc_html( sprintf( __( "If your problem isn't known or caused by a plugin, try activating %s (the default WordPress theme).", 'jetpack' ), $default_theme->get( 'Name' ) ) );
|
||||
} else {
|
||||
esc_html_e( "If your problem isn't known or caused by a plugin, try activating the default WordPress theme.", 'jetpack' );
|
||||
}
|
||||
?>
|
||||
<?php esc_html_e( "If this solves the problem, something in your theme is probably broken – let the theme's author know.", 'jetpack' ); ?>
|
||||
</li>
|
||||
<li>
|
||||
<?php esc_html_e( 'A problem with your XML-RPC file.', 'jetpack' ); ?>
|
||||
<?php
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: The URL to the site's xmlrpc.php file. */
|
||||
__( 'Load your <a href="%s">XML-RPC file</a>. It should say “XML-RPC server accepts POST requests only.” on a line by itself.', 'jetpack' ),
|
||||
array( 'a' => array( 'href' => array() ) )
|
||||
),
|
||||
esc_attr( site_url( 'xmlrpc.php' ) )
|
||||
);
|
||||
?>
|
||||
<ul>
|
||||
<li><?php esc_html_e( "If it's not by itself, a theme or plugin is displaying extra characters. Try steps 2 and 3.", 'jetpack' ); ?></li>
|
||||
<li><?php esc_html_e( 'If you get a 404 message, contact your web host. Their security may block the XML-RPC file.', 'jetpack' ); ?></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<?php if ( current_user_can( 'jetpack_disconnect' ) && Jetpack::is_connection_ready() ) : ?>
|
||||
<li>
|
||||
<?php esc_html_e( 'A connection problem with WordPress.com.', 'jetpack' ); ?>
|
||||
<?php
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: URL to disconnect and reconnect Jetpack. */
|
||||
__( 'Jetpack works by connecting to WordPress.com for a lot of features. Sometimes, when the connection gets messed up, you need to disconnect and reconnect to get things working properly. <a href="%s">Disconnect from WordPress.com</a>', 'jetpack' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'class' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
esc_attr(
|
||||
wp_nonce_url(
|
||||
Jetpack::admin_url(
|
||||
array(
|
||||
'page' => 'jetpack-debugger',
|
||||
'disconnect' => true,
|
||||
)
|
||||
),
|
||||
'jp_disconnect',
|
||||
'nonce'
|
||||
)
|
||||
)
|
||||
);
|
||||
?>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ol>
|
||||
|
||||
<h4><?php esc_html_e( 'Still having trouble?', 'jetpack' ); ?></h4>
|
||||
<p>
|
||||
<?php esc_html_e( 'Ask us for help!', 'jetpack' ); ?>
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Offload to new WordPress debug data.
|
||||
*/
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: URL for Jetpack support. URL for WordPress's Site Health */
|
||||
__( '<a href="%1$s">Contact our Happiness team</a>. When you do, please include the <a href="%2$s">full debug information from your site</a>.', 'jetpack' ),
|
||||
array( 'a' => array( 'href' => array() ) )
|
||||
),
|
||||
esc_url( $support_url ),
|
||||
esc_url( admin_url() . 'site-health.php?tab=debug' )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="jp-static-block">
|
||||
<h2><?php esc_html_e( 'More details about your Jetpack settings', 'jetpack' ); ?></h2>
|
||||
|
||||
<div class="jp-static-block-body">
|
||||
<?php if ( Jetpack::is_connection_ready() ) : ?>
|
||||
<div id="connected-user-details">
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: %s is an e-mail address */
|
||||
__( 'The primary connection is owned by <strong>%s</strong>\'s WordPress.com account.', 'jetpack' ),
|
||||
array( 'strong' => array() )
|
||||
),
|
||||
esc_html( Jetpack::get_master_user_email() )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div id="dev-mode-details">
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: Link to a Jetpack support page. */
|
||||
__( 'Would you like to use Jetpack on your local development site? You can do so thanks to <a href="%s">Jetpack\'s offline mode</a>.', 'jetpack' ),
|
||||
array( 'a' => array( 'href' => array() ) )
|
||||
),
|
||||
esc_url( Redirect::get_url( 'jetpack-support-development-mode' ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
if (
|
||||
current_user_can( 'jetpack_manage_modules' )
|
||||
&& ( ( new Status() )->is_offline_mode() || Jetpack::is_connection_ready() )
|
||||
) {
|
||||
printf(
|
||||
wp_kses(
|
||||
'<p><a href="%1$s">%2$s</a></p>',
|
||||
array(
|
||||
'a' => array( 'href' => array() ),
|
||||
'p' => array(),
|
||||
)
|
||||
),
|
||||
esc_attr( Jetpack::admin_url( 'page=jetpack_modules' ) ),
|
||||
esc_html__( 'Access the full list of Jetpack modules available on your site.', 'jetpack' )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs html needed within the <head> for the in-plugin debugger page.
|
||||
*/
|
||||
public static function jetpack_debug_admin_head() {
|
||||
|
||||
Jetpack_Admin_Page::load_wrapper_styles();
|
||||
?>
|
||||
<style type="text/css">
|
||||
|
||||
.jetpack-debug-test-container {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
#connected-user-details p strong {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.jetpack-tests-succeed {
|
||||
font-size: large;
|
||||
color: #069E08;
|
||||
}
|
||||
|
||||
.jetpack-test-details {
|
||||
margin: 4px 6px;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.formbox {
|
||||
margin: 0 0 25px 0;
|
||||
}
|
||||
|
||||
.formbox input[type="text"], .formbox input[type="email"], .formbox input[type="url"], .formbox textarea, #debug_info_div {
|
||||
border: 1px solid #dcdcde;
|
||||
border-radius: 11px;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.1);
|
||||
color: #646970;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
width: 97%;
|
||||
}
|
||||
#debug_info_div {
|
||||
border-radius: 0;
|
||||
margin-top: 16px;
|
||||
background: #FFF;
|
||||
padding: 16px;
|
||||
}
|
||||
.formbox .contact-support input[type="submit"] {
|
||||
float: right;
|
||||
margin: 0 !important;
|
||||
border-radius: 20px !important;
|
||||
cursor: pointer;
|
||||
font-size: 13pt !important;
|
||||
height: auto !important;
|
||||
margin: 0 0 2em 10px !important;
|
||||
padding: 8px 16px !important;
|
||||
background-color: #dcdcde;
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
border-top-color: rgba(255,255,255,0.1);
|
||||
border-bottom-color: rgba(0,0,0,0.15);
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.formbox span.errormsg {
|
||||
margin: 0 0 10px 10px;
|
||||
color: #d00;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.formbox.error span.errormsg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#debug_info_div, #toggle_debug_info, #debug_info_div p {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#category_div ul li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
jQuery( document ).ready( function($) {
|
||||
|
||||
$( '#debug_info' ).prepend( 'jQuery version: ' + jQuery.fn.jquery + "\r\n" );
|
||||
$( '#debug_form_info' ).prepend( 'jQuery version: ' + jQuery.fn.jquery + "\r\n" );
|
||||
|
||||
$( '.jetpack-test-error .jetpack-test-heading' ).on( 'click', function() {
|
||||
$( this ).parents( '.jetpack-test-error' ).find( '.jetpack-test-details' ).slideToggle();
|
||||
return false;
|
||||
} );
|
||||
|
||||
} );
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/**
|
||||
* WP Site Health debugging functions.
|
||||
*
|
||||
* @package automattic/jetpack
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test runner for Core's Site Health module.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*/
|
||||
function jetpack_debugger_ajax_local_testing_suite() {
|
||||
check_ajax_referer( 'health-check-site-status' );
|
||||
if ( ! current_user_can( 'jetpack_manage_modules' ) ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
$tests = new Jetpack_Cxn_Tests();
|
||||
wp_send_json_success( $tests->output_results_for_core_async_site_health() );
|
||||
}
|
||||
/**
|
||||
* Adds the Jetpack Local Testing Suite to the Core Site Health system.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*
|
||||
* @param array $core_tests Array of tests from Core's Site Health.
|
||||
*
|
||||
* @return array $core_tests Array of tests for Core's Site Health.
|
||||
*/
|
||||
function jetpack_debugger_site_status_tests( $core_tests ) {
|
||||
$cxn_tests = new Jetpack_Cxn_Tests();
|
||||
$tests = $cxn_tests->list_tests( 'direct' );
|
||||
foreach ( $tests as $test ) {
|
||||
|
||||
$core_tests['direct'][ $test['name'] ] = array(
|
||||
'label' => __( 'Jetpack: ', 'jetpack' ) . $test['name'],
|
||||
/**
|
||||
* Callable for Core's Site Health system to execute.
|
||||
*
|
||||
* @var array $test A Jetpack Testing Suite test array.
|
||||
* @var Jetpack_Cxn_Tests $cxn_tests An instance of the Jetpack Test Suite.
|
||||
*
|
||||
* @return array {
|
||||
* A results array to match the format expected by WordPress Core.
|
||||
*
|
||||
* @type string $label Name for the test.
|
||||
* @type string $status 'critical', 'recommended', or 'good'.
|
||||
* @type array $badge Array for Site Health status. Keys label and color.
|
||||
* @type string $description Description of the test result.
|
||||
* @type string $action HTML to a link to resolve issue.
|
||||
* @type string $test Unique test identifier.
|
||||
* }
|
||||
*/
|
||||
'test' => function () use ( $test, $cxn_tests ) {
|
||||
$results = $cxn_tests->run_test( $test['name'] );
|
||||
if ( is_wp_error( $results ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$label = $results['label'] ?
|
||||
$results['label'] :
|
||||
ucwords(
|
||||
str_replace(
|
||||
'_',
|
||||
' ',
|
||||
str_replace( 'test__', '', $test['name'] )
|
||||
)
|
||||
);
|
||||
if ( $results['long_description'] ) {
|
||||
$description = $results['long_description'];
|
||||
} elseif ( $results['short_description'] ) {
|
||||
$description = sprintf(
|
||||
'<p>%s</p>',
|
||||
$results['short_description']
|
||||
);
|
||||
} else {
|
||||
$description = sprintf(
|
||||
'<p>%s</p>',
|
||||
__( 'This test successfully passed!', 'jetpack' )
|
||||
);
|
||||
}
|
||||
|
||||
$return = array(
|
||||
'label' => $label,
|
||||
'status' => 'good',
|
||||
'badge' => array(
|
||||
'label' => __( 'Jetpack', 'jetpack' ),
|
||||
'color' => 'green',
|
||||
),
|
||||
'description' => $description,
|
||||
'actions' => '',
|
||||
'test' => 'jetpack_' . $test['name'],
|
||||
);
|
||||
|
||||
if ( false === $results['pass'] ) {
|
||||
$return['status'] = $results['severity'];
|
||||
if ( ! empty( $results['action'] ) ) {
|
||||
$return['actions'] = sprintf(
|
||||
'<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>',
|
||||
esc_url( $results['action'] ),
|
||||
$results['action_label'],
|
||||
/* translators: accessibility text */
|
||||
__( '(opens in a new tab)', 'jetpack' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
},
|
||||
);
|
||||
}
|
||||
$core_tests['async']['jetpack_test_suite'] = array(
|
||||
'label' => __( 'Jetpack Tests', 'jetpack' ),
|
||||
'test' => 'jetpack_local_testing_suite',
|
||||
);
|
||||
|
||||
return $core_tests;
|
||||
}
|
||||
Reference in New Issue
Block a user