PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
jetpack / jetpack_vendor / automattic / jetpack-connection / src / class-site-health.php

class-site-health.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3, at jetpack_vendor/automattic/jetpack-connection/src/class-site-health.php

260 lines 8.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WordPress Site Health integration for the Jetpack Connection package.
4 *
5 * @package automattic/jetpack-connection
6 */
7
8 namespace Automattic\Jetpack\Connection;
9
10 /**
11 * Integrates connection health tests into WordPress Site Health.
12 *
13 * This class defers to the Jetpack plugin's own debugger integration when it
14 * is present (old Jetpack versions). When no legacy integration is detected,
15 * it registers the connection health tests directly.
16 *
17 * @since 8.5.0
18 */
19 class Site_Health {
20
21 /**
22 * Whether the class has been initialized.
23 *
24 * @var bool
25 */
26 private static $initialized = false;
27
28 /**
29 * Initialize Site Health integration.
30 *
31 * Should be called once, typically from the package's actions.php via a plugins_loaded hook.
32 *
33 * @since 8.5.0
34 */
35 public static function init() {
36 if ( self::$initialized ) {
37 return;
38 }
39 self::$initialized = true;
40
41 add_action( 'admin_init', array( __CLASS__, 'maybe_register_site_health' ), 1 );
42
43 // Deliberately hooked to the cron-only `jetpack_heartbeat` action (end of
44 // Heartbeat::cron_exec()) and not `jetpack_heartbeat_stats_array`, which
45 // also fires in synchronous request contexts (XML-RPC, REST, WP-CLI) where
46 // a remote test would be slow and an amplification vector.
47 add_action( 'jetpack_heartbeat', array( __CLASS__, 'do_daily_connection_check' ) );
48 }
49
50 /**
51 * Runs the WP.com connection test once a day on the heartbeat cron.
52 *
53 * The test result itself is discarded: the point is the test's side effects,
54 * which keep the Error_Handler state for failures that WP.com cannot report
55 * through a request of its own (the site blocking WP.com requests; the site's
56 * SSL certificate failing verification) fresh — reported while the condition
57 * persists, cleared once it resolves. Without this, the errors would only
58 * update on Site Health page visits and Core's weekly Site Health cron, and
59 * would expire (ERROR_LIFE_TIME) while still unresolved.
60 *
61 * @since 8.10.0
62 */
63 public static function do_daily_connection_check() {
64 if ( ! ( new Manager() )->is_connected() ) {
65 return;
66 }
67
68 ( new Connection_Health_Tests() )->run_test( 'test__wpcom_connection_test' );
69 }
70
71 /**
72 * Conditionally register Site Health hooks.
73 *
74 * Checks whether the legacy Jetpack debugger has already registered its
75 * Site Health hooks. If so, we defer to avoid duplicate tests.
76 *
77 * @since 8.5.0
78 */
79 public static function maybe_register_site_health() {
80 // Defer to the old Jetpack plugin's debugger if it has already registered
81 // its Site Health filter. Old Jetpack versions add this filter during plugin
82 // loading, so by admin_init it is already present.
83 if ( has_filter( 'site_status_tests', 'jetpack_debugger_site_status_tests' ) ) {
84 return;
85 }
86
87 add_filter( 'site_status_tests', array( __CLASS__, 'register_site_health_tests' ) );
88 add_action( 'wp_ajax_health-check-jetpack-connection-health', array( __CLASS__, 'ajax_local_testing_suite' ) );
89 }
90
91 /**
92 * Register connection tests with WordPress Site Health.
93 *
94 * @since 8.5.0
95 *
96 * @param array $core_tests Array of tests from Core's Site Health.
97 *
98 * @return array Modified array of tests.
99 */
100 public static function register_site_health_tests( $core_tests ) {
101 $cxn_tests = new Connection_Health_Tests();
102 $tests = $cxn_tests->list_tests( 'direct' );
103
104 foreach ( $tests as $test ) {
105 $core_tests['direct'][ $test['name'] ] = array(
106 'label' => __( 'Jetpack: ', 'jetpack-connection' ) . $test['name'],
107 'test' => self::make_site_health_callback( $test, $cxn_tests ),
108 );
109 }
110
111 $core_tests['async']['jetpack_connection_test_suite'] = array(
112 'label' => __( 'Jetpack Connection Tests', 'jetpack-connection' ),
113 'test' => 'jetpack-connection-health',
114 );
115
116 return $core_tests;
117 }
118
119 /**
120 * Human-readable headings for the built-in connection tests.
121 *
122 * Used when a test result carries no label of its own, so Site Health does not
123 * derive a title from the method name (e.g. "Wpcom Connection Test").
124 *
125 * A heading here has to read sensibly for a pass, a fail, and a skip alike, so
126 * these name what was tested rather than stating an outcome. Results that want
127 * to state an outcome set their own label, which takes precedence.
128 *
129 * @since 9.4.0
130 *
131 * @return array Map of test name => label.
132 */
133 private static function get_default_test_labels() {
134 return array(
135 'test__blog_token_if_exists' => __( 'Site Token', 'jetpack-connection' ),
136 'test__check_if_connected' => __( 'WordPress.com Connection', 'jetpack-connection' ),
137 'test__master_user_exists_on_site' => __( 'Connection Owner', 'jetpack-connection' ),
138 'test__master_user_can_manage_options' => __( 'Connection Owner Permissions', 'jetpack-connection' ),
139 'test__identity_crisis' => __( 'Site Address', 'jetpack-connection' ),
140 'test__connection_token_health' => __( 'Connection Tokens', 'jetpack-connection' ),
141 'test__wpcom_connection_test' => __( 'Requests from WordPress.com', 'jetpack-connection' ),
142 // Only registered when the jetpack_debugger_run_self_test filter returns true.
143 'test__wpcom_self_test' => __( 'Site XML-RPC Endpoint', 'jetpack-connection' ),
144 'test__server_port_value' => __( 'Server Port', 'jetpack-connection' ),
145 'test__xml_parser_available' => __( 'PHP XML Support', 'jetpack-connection' ),
146 );
147 }
148
149 /**
150 * Get the heading for a test whose result carries no label of its own.
151 *
152 * Falls back to deriving a title from the method name, which is the last resort
153 * for tests other plugins register through the jetpack_connection_tests_loaded
154 * action and which therefore cannot appear in the map.
155 *
156 * @since 9.4.0
157 *
158 * @param string $test_name Test name, e.g. "test__blog_token_if_exists".
159 *
160 * @return string The heading to display.
161 */
162 private static function get_default_test_label( $test_name ) {
163 $labels = self::get_default_test_labels();
164
165 if ( isset( $labels[ $test_name ] ) ) {
166 return $labels[ $test_name ];
167 }
168
169 return ucwords(
170 str_replace(
171 '_',
172 ' ',
173 str_replace( 'test__', '', $test_name )
174 )
175 );
176 }
177
178 /**
179 * Create a closure for a Site Health direct test.
180 *
181 * @since 8.5.0
182 *
183 * @param array $test Test definition array.
184 * @param Connection_Health_Tests $cxn_tests Test suite instance.
185 *
186 * @return callable The Site Health test callback.
187 */
188 private static function make_site_health_callback( $test, $cxn_tests ) {
189 $default_label = self::get_default_test_label( $test['name'] );
190
191 return function () use ( $test, $cxn_tests, $default_label ) {
192 $results = $cxn_tests->run_test( $test['name'] );
193 if ( is_wp_error( $results ) ) {
194 return;
195 }
196
197 $label = $results['label'] ? $results['label'] : $default_label;
198
199 if ( $results['long_description'] ) {
200 $description = $results['long_description'];
201 } elseif ( $results['short_description'] ) {
202 $description = sprintf(
203 '<p>%s</p>',
204 $results['short_description']
205 );
206 } else {
207 $description = sprintf(
208 '<p>%s</p>',
209 __( 'This test successfully passed!', 'jetpack-connection' )
210 );
211 }
212
213 $badge_label = $cxn_tests->get_site_health_badge_label();
214
215 $return = array(
216 'label' => $label,
217 'status' => 'good',
218 'badge' => array(
219 'label' => $badge_label,
220 'color' => 'green',
221 ),
222 'description' => $description,
223 'actions' => '',
224 'test' => 'jetpack_' . $test['name'],
225 );
226
227 if ( false === $results['pass'] ) {
228 $return['status'] = $results['severity'];
229 if ( ! empty( $results['action'] ) ) {
230 $return['actions'] = sprintf(
231 '<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>',
232 esc_url( $results['action'] ),
233 $results['action_label'],
234 /* translators: accessibility text */
235 __( '(opens in a new tab)', 'jetpack-connection' )
236 );
237 }
238 }
239
240 return $return;
241 };
242 }
243
244 /**
245 * AJAX handler for async Site Health tests.
246 *
247 * @since 8.5.0
248 */
249 public static function ajax_local_testing_suite() {
250 check_ajax_referer( 'health-check-site-status' );
251 if ( ! current_user_can( 'manage_options' ) ) {
252 // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal
253 wp_send_json_error( null, null, JSON_UNESCAPED_SLASHES );
254 }
255 $tests = new Connection_Health_Tests();
256 // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal
257 wp_send_json_success( $tests->output_results_for_core_async_site_health(), null, JSON_UNESCAPED_SLASHES );
258 }
259 }
260