Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.36% covered (warning)
85.36%
204 / 239
74.19% covered (warning)
74.19%
23 / 31
CRAP
0.00% covered (danger)
0.00%
0 / 1
Connection_Health_Test_Base
85.36% covered (warning)
85.36%
204 / 239
74.19% covered (warning)
74.19%
23 / 31
126.55
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 add_test
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 list_tests
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
10.14
 run_test
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 run_tests
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 raw_results
75.00% covered (warning)
75.00%
9 / 12
0.00% covered (danger)
0.00%
0 / 1
9.00
 pass
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 list_fails
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 passing_test
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 skipped_test
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 informational_test
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 failing_test
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 test_result_defaults
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 helper_is_connected
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 helper_retrieve_connection_owner
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 helper_get_blog_token
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 helper_get_reconnect_url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 helper_get_support_url
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 helper_get_support_text
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 helper_get_reconnect_text
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 helper_get_timeout_text
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 helper_get_reconnect_long_description
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 connection_failing_test
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 helper_enable_outbound_requests
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 increase_timeout
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offline_mode_trigger_text
69.23% covered (warning)
69.23%
9 / 13
0.00% covered (danger)
0.00%
0 / 1
9.86
 output_results_for_cli
10.53% covered (danger)
10.53%
2 / 19
0.00% covered (danger)
0.00%
0 / 1
81.63
 output_results_for_core_async_site_health
85.71% covered (warning)
85.71%
36 / 42
0.00% covered (danger)
0.00%
0 / 1
8.19
 output_fails_as_wp_error
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
5.01
 encrypt_string_for_wpcom
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
7.07
 get_site_health_badge_label
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Base class for Jetpack Connection health tests.
4 *
5 * @package automattic/jetpack-connection
6 */
7
8namespace Automattic\Jetpack\Connection;
9
10use Automattic\Jetpack\Redirect;
11use Automattic\Jetpack\Status;
12use WP_Error;
13
14/**
15 * Base framework for connection health tests.
16 *
17 * Provides test registration, execution, result formatting, and output
18 * for WordPress Site Health integration.
19 *
20 * Individual tests should be added in the Connection_Health_Tests class.
21 *
22 * @since 8.5.0
23 */
24class Connection_Health_Test_Base {
25
26    /**
27     * Tests to run on the Jetpack connection.
28     *
29     * @var array $tests
30     */
31    protected $tests = array();
32
33    /**
34     * Results of the Jetpack connection tests.
35     *
36     * @var array $results
37     */
38    protected $results = array();
39
40    /**
41     * Status of the testing suite.
42     *
43     * Used internally to determine if a test should be skipped since the tests are already failing. Assume passing.
44     *
45     * @var bool $pass
46     */
47    protected $pass = true;
48
49    /**
50     * Connection_Health_Test_Base constructor.
51     */
52    public function __construct() {
53        $this->tests   = array();
54        $this->results = array();
55    }
56
57    /**
58     * Adds a new test to the connection testing suite.
59     *
60     * @since 8.5.0
61     *
62     * @param callable $callable Test to add to queue.
63     * @param string   $name     Unique name for the test.
64     * @param string   $type     Optional. Core Site Health type: 'direct' or 'async'. Default 'direct'.
65     * @param array    $groups   Optional. Testing groups to add test to. Default array( 'default' ).
66     *
67     * @return true|WP_Error True if successfully added. WP_Error on failure.
68     */
69    public function add_test( $callable, $name, $type = 'direct', $groups = array( 'default' ) ) {
70        if ( is_array( $name ) ) {
71            // Pre-7.3.0 method passed the $groups parameter here.
72            return new WP_Error( 'invalid_arguments', __( 'add_test arguments changed in 7.3.0. Please reference inline documentation.', 'jetpack-connection' ) );
73        }
74        if ( array_key_exists( $name, $this->tests ) ) {
75            return new WP_Error( 'duplicate_test', __( 'Test names must be unique.', 'jetpack-connection' ) );
76        }
77        if ( ! is_callable( $callable ) ) {
78            return new WP_Error( 'invalid_callable', __( 'Tests must be valid PHP callables.', 'jetpack-connection' ) );
79        }
80
81        $this->tests[ $name ] = array(
82            'name'  => $name,
83            'test'  => $callable,
84            'group' => $groups,
85            'type'  => $type,
86        );
87        return true;
88    }
89
90    /**
91     * Lists all tests to run.
92     *
93     * @since 8.5.0
94     *
95     * @param string $type  Optional. Core Site Health type: 'direct' or 'async'. 'all' by default.
96     * @param string $group Optional. A specific testing group. 'all' by default.
97     *
98     * @return array Array of tests with test information.
99     */
100    public function list_tests( $type = 'all', $group = 'all' ) {
101        if ( ! ( 'all' === $type || 'direct' === $type || 'async' === $type ) ) {
102            _doing_it_wrong( __METHOD__, 'Type must be all, direct, or async.', '' );
103        }
104
105        $tests = array();
106        foreach ( $this->tests as $name => $value ) {
107            $value_groups = isset( $value['group'] ) ? (array) $value['group'] : array();
108            if ( ( 'all' === $group || in_array( $group, $value_groups, true ) )
109                && ( 'all' === $type || $type === $value['type'] )
110            ) {
111                $tests[ $name ] = $value;
112            }
113        }
114
115        return $tests;
116    }
117
118    /**
119     * Run a specific test.
120     *
121     * @since 8.5.0
122     *
123     * @param string $name Name of test.
124     *
125     * @return array|WP_Error Test result array or WP_Error if invalid name.
126     */
127    public function run_test( $name ) {
128        if ( array_key_exists( $name, $this->tests ) ) {
129            return call_user_func( $this->tests[ $name ]['test'] );
130        }
131        return new WP_Error( 'unknown_test', __( 'There is no test by that name: ', 'jetpack-connection' ) . $name );
132    }
133
134    /**
135     * Runs the connection testing suite.
136     *
137     * @since 8.5.0
138     */
139    public function run_tests() {
140        foreach ( $this->tests as $test ) {
141            $result          = call_user_func( $test['test'] );
142            $result['group'] = $test['group'];
143            $result['type']  = $test['type'];
144            $this->results[] = $result;
145            if ( false === $result['pass'] ) {
146                $this->pass = false;
147            }
148        }
149    }
150
151    /**
152     * Returns the full results array.
153     *
154     * @since 8.5.0
155     *
156     * @param string $type  Test type, async or direct. Default 'all'.
157     * @param string $group Testing group whose results we want. Default 'all'.
158     *
159     * @return array Array of test results.
160     */
161    public function raw_results( $type = 'all', $group = 'all' ) {
162        if ( ! $this->results ) {
163            $this->run_tests();
164        }
165
166        $results = $this->results;
167
168        if ( 'all' !== $group ) {
169            foreach ( $results as $test => $result ) {
170                if ( ! in_array( $group, $result['group'], true ) ) {
171                    unset( $results[ $test ] );
172                }
173            }
174        }
175
176        if ( 'all' !== $type ) {
177            foreach ( $results as $test => $result ) {
178                if ( $type !== $result['type'] ) {
179                    unset( $results[ $test ] );
180                }
181            }
182        }
183
184        return $results;
185    }
186
187    /**
188     * Returns the status of the connection suite.
189     *
190     * @since 8.5.0
191     *
192     * @param string $type  Test type, async or direct. Default 'all'.
193     * @param string $group Testing group to check status of. Default 'all'.
194     *
195     * @return true|false True if all tests pass. False if any test fails.
196     */
197    public function pass( $type = 'all', $group = 'all' ) {
198        $results = $this->raw_results( $type, $group );
199
200        foreach ( $results as $result ) {
201            // 'pass' could be true, false, or 'skipped'. We only want false.
202            if ( isset( $result['pass'] ) && false === $result['pass'] ) {
203                return false;
204            }
205        }
206
207        return true;
208    }
209
210    /**
211     * Return array of failed test messages.
212     *
213     * @since 8.5.0
214     *
215     * @param string $type  Test type, direct or async. Default 'all'.
216     * @param string $group Testing group whose failures we want. Default 'all'.
217     *
218     * @return array Array of failed tests (may be empty).
219     */
220    public function list_fails( $type = 'all', $group = 'all' ) {
221        $results = $this->raw_results( $type, $group );
222
223        foreach ( $results as $test => $result ) {
224            if ( ! isset( $result['pass'] ) || false !== $result['pass'] || ! isset( $result['short_description'] ) ) {
225                unset( $results[ $test ] );
226            }
227        }
228
229        return $results;
230    }
231
232    /**
233     * Helper function to return consistent responses for a passing test.
234     *
235     * @since 8.5.0
236     *
237     * @param array $args Arguments to override defaults.
238     *
239     * @return array Test results.
240     */
241    public static function passing_test( $args ) {
242        $defaults                      = self::test_result_defaults();
243        $defaults['short_description'] = __( 'Test passed!', 'jetpack-connection' );
244
245        $args = wp_parse_args( $args, $defaults );
246
247        $args['pass'] = true;
248
249        return $args;
250    }
251
252    /**
253     * Helper function to return consistent responses for a skipped test.
254     *
255     * @since 8.5.0
256     *
257     * @param array $args Arguments to override defaults.
258     *
259     * @return array Test results.
260     */
261    public static function skipped_test( $args = array() ) {
262        $args = wp_parse_args(
263            $args,
264            self::test_result_defaults()
265        );
266
267        $args['pass'] = 'skipped';
268
269        return $args;
270    }
271
272    /**
273     * Helper function to return consistent responses for an informational test.
274     *
275     * @since 8.5.0
276     *
277     * @param array $args Arguments to override defaults.
278     *
279     * @return array Test results.
280     */
281    public static function informational_test( $args = array() ) {
282        $args = wp_parse_args(
283            $args,
284            self::test_result_defaults()
285        );
286
287        $args['pass'] = 'informational';
288
289        return $args;
290    }
291
292    /**
293     * Helper function to return consistent responses for a failing test.
294     *
295     * @since 8.5.0
296     *
297     * @param array $args Arguments to override defaults.
298     *
299     * @return array Test results.
300     */
301    public static function failing_test( $args ) {
302        $defaults                      = self::test_result_defaults();
303        $defaults['short_description'] = __( 'Test failed!', 'jetpack-connection' );
304        $defaults['severity']          = 'critical';
305
306        $args = wp_parse_args( $args, $defaults );
307
308        $args['pass'] = false;
309
310        return $args;
311    }
312
313    /**
314     * Provides defaults for test arguments.
315     *
316     * @since 8.5.0
317     *
318     * @return array Result defaults.
319     */
320    private static function test_result_defaults() {
321        return array(
322            'name'                => 'unnamed_test',
323            'label'               => false,
324            'short_description'   => false,
325            'long_description'    => false,
326            'severity'            => false,
327            'action'              => false,
328            'action_label'        => false,
329            'show_in_site_health' => true,
330        );
331    }
332
333    /**
334     * Helper function to check if the site is connected and not in offline mode.
335     *
336     * @return bool
337     */
338    protected function helper_is_connected() {
339        return ( new Manager() )->is_connected() && ! ( new Status() )->is_offline_mode();
340    }
341
342    /**
343     * Helper function to look up the connection owner and return the local WP_User.
344     *
345     * @return \WP_User The connection owner user.
346     */
347    protected function helper_retrieve_connection_owner() {
348        $owner_id = ( new Manager() )->get_connection_owner_id();
349        return new \WP_User( $owner_id );
350    }
351
352    /**
353     * Retrieve the blog token if it exists.
354     *
355     * @return object|false
356     */
357    protected function helper_get_blog_token() {
358        return ( new Tokens() )->get_access_token();
359    }
360
361    /**
362     * Returns the URL to reconnect.
363     *
364     * @return string The reconnect URL.
365     */
366    protected static function helper_get_reconnect_url() {
367        /**
368         * Filters the URL used to reconnect the Jetpack connection.
369         *
370         * @since 8.5.0
371         *
372         * @param string $url The reconnect URL.
373         */
374        return apply_filters( 'jetpack_connection_reconnect_url', '' );
375    }
376
377    /**
378     * Returns a support URL.
379     *
380     * @return string The support URL.
381     */
382    protected function helper_get_support_url() {
383        /**
384         * Filters the Jetpack support URL used in connection health tests.
385         *
386         * @since 8.5.0
387         *
388         * @param string $url The support URL.
389         */
390        return apply_filters(
391            'jetpack_connection_support_url',
392            Redirect::get_url( 'jetpack-contact-support' )
393        );
394    }
395
396    /**
397     * Gets translated support text.
398     *
399     * @return string
400     */
401    protected function helper_get_support_text() {
402        return __( 'Please contact Jetpack support.', 'jetpack-connection' );
403    }
404
405    /**
406     * Returns the translated text to reconnect.
407     *
408     * @return string
409     */
410    protected static function helper_get_reconnect_text() {
411        return __( 'Reconnect now', 'jetpack-connection' );
412    }
413
414    /**
415     * Returns the translated text for failing tests due to timeouts.
416     *
417     * @return string
418     */
419    protected static function helper_get_timeout_text() {
420        return __( 'The test timed out which may sometimes indicate a failure or may be a false failure. Please relaunch tests.', 'jetpack-connection' );
421    }
422
423    /**
424     * Gets translated reconnect long description.
425     *
426     * @param string $connection_error  The connection specific error.
427     * @param string $recommendation   The recommendation for resolving the connection error.
428     *
429     * @return string The translated long description.
430     */
431    protected static function helper_get_reconnect_long_description( $connection_error, $recommendation ) {
432        return sprintf(
433            '<p>%1$s</p>' .
434            '<p><span class="dashicons fail"><span class="screen-reader-text">%2$s</span></span> %3$s</p><p><strong>%4$s</strong></p>',
435            __( 'A healthy Jetpack Connection allows connected plugins (such as Jetpack and WooCommerce) to provide features like Stats, Site Security, and Payments.', 'jetpack-connection' ),
436            /* translators: screen reader text indicating a test failed */
437            __( 'Error', 'jetpack-connection' ),
438            $connection_error,
439            $recommendation
440        );
441    }
442
443    /**
444     * Helper function to return consistent responses for a connection failing test.
445     *
446     * @param string $name             The test method name.
447     * @param string $connection_error The connection specific error.
448     * @param string $recommendation   The recommendation for resolving the connection error.
449     *
450     * @return array Test results.
451     */
452    public static function connection_failing_test( $name, $connection_error = '', $recommendation = '' ) {
453        $connection_error = empty( $connection_error ) ? __( 'Your site is not connected to WordPress.com.', 'jetpack-connection' ) : $connection_error;
454        $recommendation   = empty( $recommendation ) ? __( 'We recommend reconnecting your site to WordPress.com.', 'jetpack-connection' ) : $recommendation;
455
456        $reconnect_url = self::helper_get_reconnect_url();
457
458        $args = array(
459            'name'              => $name,
460            'short_description' => $connection_error,
461            'long_description'  => self::helper_get_reconnect_long_description( $connection_error, $recommendation ),
462        );
463
464        if ( ! empty( $reconnect_url ) ) {
465            $args['action']       = $reconnect_url;
466            $args['action_label'] = self::helper_get_reconnect_text();
467        }
468
469        return self::failing_test( $args );
470    }
471
472    /**
473     * Gets translated text to enable outbound requests.
474     *
475     * @param string $protocol Either 'HTTP' or 'HTTPS'.
476     *
477     * @return string
478     */
479    protected function helper_enable_outbound_requests( $protocol ) {
480        return sprintf(
481            /* translators: %1$s - request protocol, either http or https */
482            __(
483                'Your server did not successfully connect to WordPress.com using %1$s.
484                Please ask your hosting provider to confirm your server can make outbound requests to WordPress.com.',
485                'jetpack-connection'
486            ),
487            $protocol
488        );
489    }
490
491    /**
492     * Returns 30 for use with a filter to increase HTTP request timeout.
493     *
494     * @return int 30
495     */
496    public static function increase_timeout() {
497        return 30;
498    }
499
500    /**
501     * Returns a human-readable explanation of why the site is in offline mode.
502     *
503     * @since 8.5.0
504     *
505     * @return string The offline mode trigger explanation.
506     */
507    public static function offline_mode_trigger_text() {
508        $status = new Status();
509
510        if ( ! $status->is_offline_mode() ) {
511            return __( 'The site is not in Offline Mode.', 'jetpack-connection' );
512        }
513
514        if ( defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG ) {
515            $notice = __( 'The JETPACK_DEV_DEBUG constant is defined in wp-config.php or elsewhere.', 'jetpack-connection' );
516        } elseif ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) {
517            $notice = __( 'The WP_LOCAL_DEV constant is defined in wp-config.php or elsewhere.', 'jetpack-connection' );
518        } elseif ( $status->is_local_site() ) {
519            $notice = __( 'The site URL is a known local development environment URL (e.g. http://localhost).', 'jetpack-connection' );
520        } elseif ( get_option( 'jetpack_offline_mode' ) ) {
521            $notice = __( 'The jetpack_offline_mode option is set to true.', 'jetpack-connection' );
522        } else {
523            $notice = __( 'The jetpack_offline_mode filter is set to true.', 'jetpack-connection' );
524        }
525
526        return $notice;
527    }
528
529    /**
530     * Provide WP_CLI friendly testing results.
531     *
532     * @since 8.5.0
533     *
534     * @param string $group Testing group whose results we are outputting. Default 'all'.
535     */
536    public function output_results_for_cli( $group = 'all' ) {
537        if ( ! ( defined( 'WP_CLI' ) && WP_CLI ) ) {
538            return;
539        }
540
541        if ( ( new Status() )->is_offline_mode() ) {
542            \WP_CLI::line( __( 'Your site is in Offline Mode:', 'jetpack-connection' ) );
543            \WP_CLI::line( self::offline_mode_trigger_text() );
544        }
545        \WP_CLI::line( __( 'TEST RESULTS:', 'jetpack-connection' ) );
546        foreach ( $this->raw_results( 'all', $group ) as $test ) {
547            if ( true === $test['pass'] ) {
548                \WP_CLI::log( \WP_CLI::colorize( '%gPassed:%n  ' . $test['name'] ) );
549            } elseif ( 'skipped' === $test['pass'] ) {
550                \WP_CLI::log( \WP_CLI::colorize( '%ySkipped:%n ' . $test['name'] ) );
551                if ( $test['short_description'] ) {
552                    \WP_CLI::log( '         ' . $test['short_description'] );
553                }
554            } elseif ( 'informational' === $test['pass'] ) {
555                \WP_CLI::log( \WP_CLI::colorize( '%yInfo:%n    ' . $test['name'] ) );
556                if ( $test['short_description'] ) {
557                    \WP_CLI::log( '         ' . $test['short_description'] );
558                }
559            } else {
560                \WP_CLI::log( \WP_CLI::colorize( '%rFailed:%n  ' . $test['name'] ) );
561                \WP_CLI::log( '         ' . $test['short_description'] );
562            }
563        }
564    }
565
566    /**
567     * Output results of failures in format expected by Core's Site Health tool for async tests.
568     *
569     * @since 8.5.0
570     *
571     * @return array Array of test results.
572     */
573    public function output_results_for_core_async_site_health() {
574        $badge_label = $this->get_site_health_badge_label();
575
576        $result = array(
577            'label'       => __( 'Jetpack Connection passed all async tests.', 'jetpack-connection' ),
578            'status'      => 'good',
579            'badge'       => array(
580                'label' => $badge_label,
581                'color' => 'green',
582            ),
583            'description' => sprintf(
584                '<p>%s</p>',
585                __( "The Jetpack Connection's async local testing suite passed all tests!", 'jetpack-connection' )
586            ),
587            'actions'     => '',
588            'test'        => 'jetpack_connection_local_testing_suite',
589        );
590
591        if ( $this->pass() ) {
592            return $result;
593        }
594
595        $fails = $this->list_fails( 'async' );
596        $error = false;
597        foreach ( $fails as $fail ) {
598            if ( ! $error ) {
599                $error                 = true;
600                $result['label']       = $fail['short_description'];
601                $result['status']      = $fail['severity'];
602                $result['description'] = sprintf(
603                    '<p>%s</p>',
604                    $fail['long_description'] ? $fail['long_description'] : $fail['short_description']
605                );
606                if ( ! empty( $fail['action'] ) ) {
607                    $result['actions'] = sprintf(
608                        '<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>',
609                        esc_url( $fail['action'] ),
610                        $fail['action_label'] ? $fail['action_label'] : __( 'Resolve', 'jetpack-connection' ),
611                        /* translators: accessibility text */
612                        __( '(opens in a new tab)', 'jetpack-connection' )
613                    );
614                }
615            } else {
616                $result['description'] .= sprintf(
617                    '<p>%s</p>',
618                    __( 'There was another problem:', 'jetpack-connection' )
619                ) . ' ' . $fail['short_description'];
620                if ( 'critical' === $fail['severity'] ) {
621                    $result['status'] = 'critical';
622                }
623            }
624        }
625
626        return $result;
627    }
628
629    /**
630     * Provide single WP_Error instance of all failures.
631     *
632     * @since 8.5.0
633     *
634     * @param string $type  Test type, direct or async. Default 'all'.
635     * @param string $group Testing group whose failures we want converted. Default 'all'.
636     *
637     * @return WP_Error|false WP_Error with all failed tests or false if no failures.
638     */
639    public function output_fails_as_wp_error( $type = 'all', $group = 'all' ) {
640        if ( $this->pass( $type, $group ) ) {
641            return false;
642        }
643        $fails = $this->list_fails( $type, $group );
644        $error = false;
645
646        foreach ( $fails as $result ) {
647            $code    = 'failed_' . $result['name'];
648            $message = $result['short_description'];
649            $data    = array(
650                'resolution' => $result['action'] ?
651                    $result['action_label'] . ' :' . $result['action'] :
652                    '',
653            );
654            if ( ! $error ) {
655                $error = new WP_Error( $code, $message, $data );
656            } else {
657                $error->add( $code, $message, $data );
658            }
659        }
660
661        return $error;
662    }
663
664    /**
665     * Encrypt data for sending to WordPress.com.
666     *
667     * @since 8.5.0
668     *
669     * @param string $data Data to encrypt with the WP.com Public Key.
670     *
671     * @return false|array False if functionality not available. Array of encrypted data, encryption key.
672     */
673    public function encrypt_string_for_wpcom( $data ) {
674        $return = false;
675        if ( ! function_exists( 'openssl_get_publickey' ) || ! function_exists( 'openssl_seal' ) ) {
676            return $return;
677        }
678
679        $public_key = openssl_get_publickey( REST_Connector::JETPACK__DEBUGGER_PUBLIC_KEY );
680
681        // Select the first allowed cipher method.
682        $allowed_methods = array( 'aes-256-ctr', 'aes-256-cbc' );
683        $methods         = array_intersect( $allowed_methods, openssl_get_cipher_methods() );
684        $method          = array_shift( $methods );
685
686        $iv = '';
687        // @phan-suppress-next-line PhanTypeMismatchArgumentInternal -- $env_key is populated by reference by openssl_seal().
688        if ( $public_key && $method && openssl_seal( $data, $encrypted_data, $env_key, array( $public_key ), $method, $iv ) ) {
689            // We are returning base64-encoded values to ensure they're characters we can use in JSON responses without issue.
690            $return = array(
691                'data'   => base64_encode( $encrypted_data ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
692                'key'    => base64_encode( $env_key[0] ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- @phan-suppress-current-line PhanTypeArraySuspiciousNullable
693                'iv'     => base64_encode( $iv ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
694                'cipher' => strtoupper( $method ),
695            );
696        }
697
698        // openssl_free_key was deprecated as no longer needed in PHP 8.0+. Can remove when PHP 8.0 is our minimum. (lol).
699        if ( PHP_VERSION_ID < 80000 ) {
700            // @phan-suppress-next-line PhanDeprecatedFunctionInternal
701            openssl_free_key( $public_key ); // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.openssl_free_keyDeprecated, Generic.PHP.DeprecatedFunctions.Deprecated
702        }
703
704        return $return;
705    }
706
707    /**
708     * Returns the badge label for Site Health integration.
709     *
710     * @since 8.5.0
711     *
712     * @return string The badge label.
713     */
714    public function get_site_health_badge_label() {
715        /**
716         * Filters the badge label shown in WordPress Site Health for connection tests.
717         *
718         * @since 8.5.0
719         *
720         * @param string $label The badge label. Default 'Jetpack Connection'.
721         */
722        return apply_filters( 'jetpack_connection_site_health_badge_label', __( 'Jetpack Connection', 'jetpack-connection' ) );
723    }
724}