PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-connection / src / class-heartbeat.php
jetpack / jetpack_vendor / automattic / jetpack-connection / src Last commit date
abilities 3 days ago connectors 3 days ago health 3 days ago identity-crisis 1 month ago sso 1 month ago traits 8 months ago webhooks 8 months ago class-authorize-json-api.php 1 month ago class-client.php 3 days ago class-connection-assets.php 1 year ago class-connection-notice.php 8 months ago class-error-handler.php 3 days ago class-external-storage.php 4 months ago class-heartbeat.php 1 month ago class-initial-state.php 3 weeks ago class-manager.php 3 days ago class-nonce-handler.php 8 months ago class-package-version-tracker.php 1 month ago class-package-version.php 3 days ago class-partner-coupon.php 2 months ago class-partner.php 2 years ago class-plugin-storage.php 8 months ago class-plugin.php 8 months ago class-rest-authentication.php 3 days ago class-rest-connector.php 3 days ago class-secrets.php 8 months ago class-server-sandbox.php 2 months ago class-site-health.php 3 days ago class-terms-of-service.php 3 days ago class-tokens-locks.php 8 months ago class-tokens.php 3 days ago class-tracking.php 3 days ago class-urls.php 6 months ago class-user-account-status.php 3 days ago class-users-connection-admin.php 2 months ago class-utils.php 2 years ago class-webhooks.php 1 month ago class-xmlrpc-async-call.php 2 years ago class-xmlrpc-connector.php 8 months ago interface-manager.php 4 years ago interface-storage-provider.php 6 months ago
class-heartbeat.php
485 lines
1 <?php
2 /**
3 * Jetpack Heartbeat package.
4 *
5 * @package automattic/jetpack-connection
6 */
7
8 namespace Automattic\Jetpack;
9
10 use Automattic\Jetpack\Connection\Rest_Authentication;
11 use Automattic\Jetpack\Connection\REST_Connector;
12 use Automattic\Jetpack\Connection\Utils;
13 use Jetpack_Options;
14 use WP_CLI;
15 use WP_Error;
16 use WP_REST_Request;
17 use WP_REST_Server;
18
19 /**
20 * Heartbeat sends a batch of stats to wp.com once a day
21 */
22 class Heartbeat {
23
24 /**
25 * Holds the singleton instance of this class
26 *
27 * @since 1.0.0
28 * @since-jetpack 2.3.3
29 * @var Heartbeat
30 */
31 private static $instance = false;
32
33 /**
34 * Cronjob identifier
35 *
36 * @var string
37 */
38 private $cron_name = 'jetpack_v2_heartbeat';
39
40 /**
41 * Singleton
42 *
43 * @since 1.0.0
44 * @since-jetpack 2.3.3
45 * @static
46 * @return Heartbeat
47 */
48 public static function init() {
49 if ( ! self::$instance ) {
50 self::$instance = new Heartbeat();
51 }
52
53 return self::$instance;
54 }
55
56 /**
57 * Constructor for singleton
58 *
59 * @since 1.0.0
60 * @since-jetpack 2.3.3
61 */
62 private function __construct() {
63
64 // Schedule the task.
65 add_action( $this->cron_name, array( $this, 'cron_exec' ) );
66
67 if ( ! wp_next_scheduled( $this->cron_name ) ) {
68 // Deal with the old pre-3.0 weekly one.
69 $timestamp = wp_next_scheduled( 'jetpack_heartbeat' );
70 if ( $timestamp ) {
71 wp_unschedule_event( $timestamp, 'jetpack_heartbeat' );
72 }
73
74 wp_schedule_event( time(), 'daily', $this->cron_name );
75 }
76
77 add_filter( 'jetpack_xmlrpc_unauthenticated_methods', array( __CLASS__, 'jetpack_xmlrpc_methods' ) );
78
79 if ( defined( 'WP_CLI' ) && WP_CLI ) {
80 WP_CLI::add_command( 'jetpack-heartbeat', array( $this, 'cli_callback' ) );
81 }
82
83 add_action( 'rest_api_init', array( $this, 'initialize_rest_api' ) );
84 }
85
86 /**
87 * Method that gets executed on the wp-cron call
88 *
89 * @since 1.0.0
90 * @since-jetpack 2.3.3
91 * @global string $wp_version
92 */
93 public function cron_exec() {
94
95 $a8c_mc_stats = new A8c_Mc_Stats();
96
97 /*
98 * This should run daily. Figuring in for variances in
99 * WP_CRON, don't let it run more than every 23 hours at most.
100 *
101 * i.e. if it ran less than 23 hours ago, fail out.
102 */
103 $last = (int) Jetpack_Options::get_option( 'last_heartbeat' );
104 if ( $last && ( $last + DAY_IN_SECONDS - HOUR_IN_SECONDS > time() ) ) {
105 return;
106 }
107
108 /*
109 * Check for an identity crisis
110 *
111 * If one exists:
112 * - Bump stat for ID crisis
113 * - Email site admin about potential ID crisis
114 */
115
116 // Coming Soon!
117
118 foreach ( self::generate_stats_array( 'v2-' ) as $key => $value ) {
119 if ( is_array( $value ) ) {
120 foreach ( $value as $v ) {
121 $a8c_mc_stats->add( $key, (string) $v );
122 }
123 } else {
124 $a8c_mc_stats->add( $key, (string) $value );
125 }
126 }
127
128 Jetpack_Options::update_option( 'last_heartbeat', time() );
129
130 $a8c_mc_stats->do_server_side_stats();
131
132 /**
133 * Fires when we synchronize all registered options on heartbeat.
134 *
135 * @since 3.3.0
136 */
137 do_action( 'jetpack_heartbeat' );
138 }
139
140 /**
141 * Generates heartbeat stats data.
142 *
143 * @param string $prefix Prefix to add before stats identifier.
144 *
145 * @return array The stats array.
146 */
147 public static function generate_stats_array( $prefix = '' ) {
148
149 /**
150 * This filter is used to build the array of stats that are bumped once a day by Jetpack Heartbeat.
151 *
152 * Filter the array and add key => value pairs where
153 * * key is the stat group name
154 * * value is the stat name.
155 *
156 * Example:
157 * add_filter( 'jetpack_heartbeat_stats_array', function( $stats ) {
158 * $stats['is-https'] = is_ssl() ? 'https' : 'http';
159 * });
160 *
161 * This will bump the stats for the 'is-https/https' or 'is-https/http' stat.
162 *
163 * @param array $stats The stats to be filtered.
164 * @param string $prefix The prefix that will automatically be added at the begining at each stat group name.
165 */
166 $stats = apply_filters( 'jetpack_heartbeat_stats_array', array(), $prefix );
167 $return = array();
168
169 // Apply prefix to stats.
170 foreach ( $stats as $stat => $value ) {
171 $return[ "$prefix$stat" ] = $value;
172 }
173
174 return $return;
175 }
176
177 /**
178 * Generates the site environment stats that are reported in the heartbeat.
179 *
180 * These describe the host environment (WordPress/PHP versions, site configuration, etc.)
181 * rather than the Jetpack plugin itself, so they live in the Connection package and are
182 * reported for every connected site, including standalone-connection installs.
183 *
184 * @since 8.7.9
185 *
186 * @return array The environment stats array, keyed by unprefixed stat name.
187 */
188 public static function get_environment_stats() {
189 $stats = array();
190
191 $stats['wp-version'] = get_bloginfo( 'version' );
192 $stats['php-version'] = PHP_VERSION;
193 $stats['wp-branch'] = (float) get_bloginfo( 'version' );
194 $stats['php-branch'] = (float) PHP_VERSION;
195 $stats['public'] = Jetpack_Options::get_option( 'public' );
196 $stats['ssl'] = self::permit_ssl();
197 $stats['is-https'] = is_ssl() ? 'https' : 'http';
198 $stats['language'] = get_bloginfo( 'language' );
199 $stats['charset'] = get_bloginfo( 'charset' );
200 $stats['is-multisite'] = is_multisite() ? 'multisite' : 'singlesite';
201 $stats['plugins'] = implode( ',', self::get_active_plugins() );
202
203 if ( function_exists( 'get_mu_plugins' ) ) {
204 $stats['mu-plugins'] = implode( ',', array_keys( get_mu_plugins() ) );
205 }
206
207 if ( function_exists( 'get_space_used' ) ) { // Only available in multisite.
208 $space_used = get_space_used();
209 } else {
210 // This is the same as `get_space_used`, except it does not apply the short-circuit filter.
211 $upload_dir = wp_upload_dir();
212 $space_used = get_dirsize( $upload_dir['basedir'] ) / MB_IN_BYTES;
213 }
214
215 $stats['space-used'] = $space_used;
216
217 // is-multi-network can have three values, `single-site`, `single-network`, and `multi-network`.
218 $stats['is-multi-network'] = 'single-site';
219 if ( is_multisite() ) {
220 $stats['is-multi-network'] = ( new Status() )->is_multi_network() ? 'multi-network' : 'single-network';
221 }
222
223 if ( ! empty( $_SERVER['SERVER_ADDR'] ) || ! empty( $_SERVER['LOCAL_ADDR'] ) ) {
224 $ip = ! empty( $_SERVER['SERVER_ADDR'] ) ? wp_unslash( $_SERVER['SERVER_ADDR'] ) : wp_unslash( $_SERVER['LOCAL_ADDR'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized just below.
225 $ip_arr = array_map( 'intval', explode( '.', $ip ) );
226 if ( 4 === count( $ip_arr ) ) {
227 $stats['ip-2-octets'] = implode( '.', array_slice( $ip_arr, 0, 2 ) );
228 }
229 }
230
231 return $stats;
232 }
233
234 /**
235 * Checks whether the site can connect to WordPress.com over SSL.
236 *
237 * This is the canonical SSL connectivity check. It caches both the boolean result (in the
238 * `jetpack_https_test` transient) and a structured failure reason (via
239 * {@see self::get_ssl_test_error()}) so a single check serves both the `ssl` heartbeat stat
240 * and consumers such as the Jetpack plugin's admin notice, which renders a localized message
241 * from the reason code. This avoids duplicate network checks and keeps translated strings out
242 * of the package.
243 *
244 * @since 8.7.9
245 *
246 * @param bool $force_recheck Force the SSL recheck instead of using the cached result.
247 * @return bool Whether the site can connect to WordPress.com over SSL.
248 */
249 public static function permit_ssl( $force_recheck = false ) {
250 $ssl = false;
251 if ( ! $force_recheck ) {
252 $ssl = get_transient( 'jetpack_https_test' );
253 }
254
255 if ( $force_recheck || false === $ssl ) {
256 $error = array(
257 'code' => '',
258 'detail' => '',
259 );
260
261 $api_base = Constants::get_constant( 'JETPACK__API_BASE' );
262 if ( ! $api_base ) {
263 $api_base = Utils::DEFAULT_JETPACK__API_BASE;
264 }
265
266 if ( ! str_starts_with( $api_base, 'https' ) ) {
267 $ssl = 0;
268 } else {
269 $ssl = 1;
270
271 if ( ! wp_http_supports( array( 'ssl' => true ) ) ) {
272 $ssl = 0;
273 $error['code'] = 'no_ssl_support';
274 } else {
275 $response = wp_remote_get( $api_base . 'test/1/' );
276 if ( is_wp_error( $response ) ) {
277 $ssl = 0;
278 $error['code'] = 'no_ssl_support';
279 } elseif ( 'OK' !== wp_remote_retrieve_body( $response ) ) {
280 $ssl = 0;
281 $error['code'] = 'bad_response';
282 $error['detail'] = wp_remote_retrieve_body( $response );
283 }
284 }
285 }
286 set_transient( 'jetpack_https_test', $ssl, DAY_IN_SECONDS );
287 set_transient( 'jetpack_https_test_error', $error, DAY_IN_SECONDS );
288 }
289
290 return (bool) $ssl;
291 }
292
293 /**
294 * Returns the structured reason for the last SSL connectivity failure.
295 *
296 * Consumers can map the returned reason code to a localized message. The `detail` value
297 * carries any additional context (e.g. the unexpected response body for `bad_response`).
298 *
299 * @since 8.7.9
300 *
301 * @return array {
302 * The last SSL test error.
303 *
304 * @type string $code Reason code: '' (no error), 'no_ssl_support', or 'bad_response'.
305 * @type string $detail Additional context for the failure, if any.
306 * }
307 */
308 public static function get_ssl_test_error() {
309 $error = get_transient( 'jetpack_https_test_error' );
310
311 if ( ! is_array( $error ) ) {
312 $error = array();
313 }
314
315 return array(
316 'code' => isset( $error['code'] ) ? (string) $error['code'] : '',
317 'detail' => isset( $error['detail'] ) ? (string) $error['detail'] : '',
318 );
319 }
320
321 /**
322 * Gets all plugins currently active, regardless of whether they're traditionally
323 * activated or network activated.
324 *
325 * Ported from the Jetpack plugin so the `plugins` heartbeat stat can be generated from
326 * the Connection package. This is the canonical implementation; the Jetpack plugin's
327 * `Jetpack::get_active_plugins()` delegates to it.
328 *
329 * @since 8.7.9
330 *
331 * @return array
332 */
333 public static function get_active_plugins() {
334 $active_plugins = (array) get_option( 'active_plugins', array() );
335
336 if ( is_multisite() ) {
337 // Due to legacy code, active_sitewide_plugins stores them in the keys,
338 // whereas active_plugins stores them in the values.
339 $network_plugins = array_keys( get_site_option( 'active_sitewide_plugins', array() ) );
340 if ( $network_plugins ) {
341 $active_plugins = array_merge( $active_plugins, $network_plugins );
342 }
343 }
344
345 sort( $active_plugins );
346
347 return array_unique( $active_plugins );
348 }
349
350 /**
351 * Registers jetpack.getHeartbeatData xmlrpc method
352 *
353 * @param array $methods The list of methods to be filtered.
354 * @return array $methods
355 */
356 public static function jetpack_xmlrpc_methods( $methods ) {
357 $methods['jetpack.getHeartbeatData'] = array( __CLASS__, 'xmlrpc_data_response' );
358 return $methods;
359 }
360
361 /**
362 * Handles the response for the jetpack.getHeartbeatData xmlrpc method
363 *
364 * @param array $params The parameters received in the request.
365 * @return array $params all the stats that heartbeat handles.
366 */
367 public static function xmlrpc_data_response( $params = array() ) {
368 // The WordPress XML-RPC server sets a default param of array()
369 // if no argument is passed on the request and the method handlers get this array in $params.
370 // generate_stats_array() needs a string as first argument.
371 $params = empty( $params ) ? '' : $params;
372 return self::generate_stats_array( $params );
373 }
374
375 /**
376 * Clear scheduled events
377 *
378 * @return void
379 */
380 public function deactivate() {
381 // Deal with the old pre-3.0 weekly one.
382 $timestamp = wp_next_scheduled( 'jetpack_heartbeat' );
383 if ( $timestamp ) {
384 wp_unschedule_event( $timestamp, 'jetpack_heartbeat' );
385 }
386
387 $timestamp = wp_next_scheduled( $this->cron_name );
388 wp_unschedule_event( $timestamp, $this->cron_name );
389 }
390
391 /**
392 * Interact with the Heartbeat
393 *
394 * ## OPTIONS
395 *
396 * inspect (default): Gets the list of data that is going to be sent in the heartbeat and the date/time of the last heartbeat
397 *
398 * @param array $args Arguments passed via CLI.
399 *
400 * @return void
401 */
402 public function cli_callback( $args ) {
403
404 $allowed_args = array(
405 'inspect',
406 );
407
408 if ( isset( $args[0] ) && ! in_array( $args[0], $allowed_args, true ) ) {
409 /* translators: %s is a command like "prompt" */
410 WP_CLI::error( sprintf( __( '%s is not a valid command.', 'jetpack-connection' ), $args[0] ) );
411 }
412
413 $stats = self::generate_stats_array();
414 $formatted_stats = array();
415
416 foreach ( $stats as $stat_name => $bin ) {
417 $formatted_stats[] = array(
418 'Stat name' => $stat_name,
419 'Bin' => $bin,
420 );
421 }
422
423 WP_CLI\Utils\format_items( 'table', $formatted_stats, array( 'Stat name', 'Bin' ) );
424
425 $last_heartbeat = Jetpack_Options::get_option( 'last_heartbeat' );
426
427 if ( $last_heartbeat ) {
428 $last_date = gmdate( 'Y-m-d H:i:s', $last_heartbeat );
429 /* translators: %s is the full datetime of the last heart beat e.g. 2020-01-01 12:21:23 */
430 WP_CLI::line( sprintf( __( 'Last heartbeat sent at: %s', 'jetpack-connection' ), $last_date ) );
431 }
432 }
433
434 /**
435 * Initialize the heartbeat REST API.
436 *
437 * @return void
438 */
439 public function initialize_rest_api() {
440 register_rest_route(
441 'jetpack/v4',
442 '/heartbeat/data',
443 array(
444 'methods' => WP_REST_Server::READABLE,
445 'callback' => array( $this, 'rest_heartbeat_data' ),
446 'permission_callback' => array( $this, 'rest_heartbeat_data_permission_check' ),
447 'args' => array(
448 'prefix' => array(
449 'description' => __( 'Prefix to add before the stats identifiers.', 'jetpack-connection' ),
450 'type' => 'string',
451 ),
452 ),
453 )
454 );
455 }
456
457 /**
458 * Endpoint to retrieve the heartbeat data.
459 *
460 * @param WP_REST_Request $request The request data.
461 *
462 * @since 2.7.0
463 *
464 * @return array
465 */
466 public function rest_heartbeat_data( WP_REST_Request $request ) {
467 return static::generate_stats_array( $request->get_param( 'prefix' ) );
468 }
469
470 /**
471 * Check permissions for the `get_heartbeat_data` endpoint.
472 *
473 * @return true|WP_Error
474 */
475 public function rest_heartbeat_data_permission_check() {
476 if ( current_user_can( 'jetpack_connect' ) ) {
477 return true;
478 }
479
480 return Rest_Authentication::is_signed_with_blog_token()
481 ? true
482 : new WP_Error( 'invalid_permission_heartbeat_data', REST_Connector::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
483 }
484 }
485