Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.75% covered (success)
93.75%
60 / 64
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Scheduled_Updates_Health_Paths
93.75% covered (success)
93.75%
60 / 64
80.00% covered (warning)
80.00%
4 / 5
20.10
0.00% covered (danger)
0.00%
0 / 1
 get
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 update
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 clear
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 validate
82.61% covered (warning)
82.61%
19 / 23
0.00% covered (danger)
0.00%
0 / 1
11.64
 add_health_check_paths_field
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Scheduled Updates Health Paths class
4 *
5 * @package automattic/scheduled-updates
6 */
7
8namespace Automattic\Jetpack;
9
10use WP_Error;
11
12/**
13 * Scheduled_Updates_Health_Paths class
14 *
15 * This class provides static methods to get/save health paths for scheduled updates.
16 */
17class Scheduled_Updates_Health_Paths {
18
19    /**
20     * The name of the WordPress option where the health check paths are stored.
21     */
22    const OPTION_NAME = 'jetpack_scheduled_update_health_check_paths';
23
24    /**
25     * Get the health check paths for a scheduled update.
26     *
27     * @param string $schedule_id Request ID.
28     * @return array List of health check paths.
29     */
30    public static function get( $schedule_id ) {
31        $option = get_option( self::OPTION_NAME, array() );
32
33        return $option[ $schedule_id ] ?? array();
34    }
35
36    /**
37     * Update the health check paths for a scheduled update.
38     *
39     * @param string $schedule_id Request ID.
40     * @param array  $paths       List of paths to save.
41     * @return bool
42     */
43    public static function update( $schedule_id, $paths ) {
44        $option       = get_option( self::OPTION_NAME, array() );
45        $parsed_paths = array();
46
47        foreach ( $paths as $path ) {
48            $parsed = self::validate( $path );
49
50            if ( is_string( $parsed ) ) {
51                $parsed_paths[] = $parsed;
52            }
53        }
54
55        $parsed_paths = array_values( array_unique( $parsed_paths ) );
56
57        if ( count( $parsed_paths ) ) {
58            $option[ $schedule_id ] = $parsed_paths;
59        }
60
61        return update_option( self::OPTION_NAME, $option );
62    }
63
64    /**
65     * Clear the health check paths for a scheduled update.
66     *
67     * @param string|null $schedule_id Request ID.
68     * @return bool
69     */
70    public static function clear( $schedule_id ) {
71        $option = get_option( self::OPTION_NAME, array() );
72
73        if ( isset( $option[ $schedule_id ] ) ) {
74            unset( $option[ $schedule_id ] );
75        }
76
77        if ( count( $option ) ) {
78            return update_option( self::OPTION_NAME, $option );
79        } else {
80            return delete_option( self::OPTION_NAME );
81        }
82    }
83
84    /**
85     * Validate a path.
86     *
87     * @param string $path Path to validate.
88     * @return string|WP_Error
89     */
90    public static function validate( $path ) {
91        if ( ! is_string( $path ) ) {
92            return new WP_Error( 'rest_invalid_path', __( 'The path must be a string.', 'jetpack-scheduled-updates' ) );
93        }
94
95        $site_url = wp_parse_url( get_site_url() );
96        $path     = trim( $path );
97
98        if (
99            ! str_starts_with( $path, $site_url['host'] ) &&
100            ! str_starts_with( $path, $site_url['scheme'] . '://' . $site_url['host'] )
101        ) {
102            // The user sent 'test/test.php' instead of '/test/test.php' and not
103            // 'http://example.com/test/test.php' or 'example.com/test/test.php'.
104            $path = '/' . ltrim( $path, '/\\' );
105        }
106
107        $path   = esc_url_raw( trim( $path ) );
108        $parsed = wp_parse_url( $path );
109
110        if ( false === $parsed ) {
111            return new WP_Error( 'rest_invalid_path', __( 'The path must be a valid URL.', 'jetpack-scheduled-updates' ) );
112        }
113
114        if ( array_key_exists( 'host', $parsed ) ) {
115            if ( $site_url['host'] !== $parsed['host'] ) {
116                return new WP_Error( 'rest_invalid_path', __( 'The URL is not from the current site.', 'jetpack-scheduled-updates' ) );
117            }
118
119            if ( array_key_exists( 'scheme', $parsed ) && $site_url['scheme'] !== $parsed['scheme'] ) {
120                return new WP_Error( 'rest_invalid_path', __( 'The URL scheme must match the current site.', 'jetpack-scheduled-updates' ) );
121            }
122        }
123
124        if ( ! array_key_exists( 'path', $parsed ) ) {
125            $parsed['path'] = '';
126        } else {
127            $parsed['path'] = trim( $parsed['path'] );
128        }
129
130        $ret = '/' . ltrim( $parsed['path'], '/\\' );
131
132        if ( array_key_exists( 'query', $parsed ) ) {
133            $ret .= '?' . trim( $parsed['query'] );
134        }
135
136        return $ret;
137    }
138
139    /**
140     * Registers the health_check_paths field for the update-schedule REST API.
141     */
142    public static function add_health_check_paths_field() {
143        register_rest_field(
144            'update-schedule',
145            'health_check_paths',
146            array(
147                /**
148                 * Populates the health_check_paths field.
149                 *
150                 * @param array $item Prepared response array.
151                 * @return array List of health check paths.
152                 */
153                'get_callback'    => function ( $item ) {
154                    return static::get( $item['schedule_id'] );
155                },
156
157                /**
158                 * Updates the health_check_paths field.
159                 *
160                 * @param array  $paths List of health check paths.
161                 * @param object $event Event object.
162                 * @return bool
163                 */
164                'update_callback' => function ( $paths, $event ) {
165                    return static::update( $event->schedule_id, $paths );
166                },
167                'schema'          => array(
168                    'description' => 'List of paths to check for site health after the update.',
169                    'type'        => 'array',
170                    'maxItems'    => 5,
171                    'items'       => array(
172                        'type'        => 'string',
173                        'arg_options' => array(
174                            'validate_callback' => array( __CLASS__, 'validate' ),
175                        ),
176                    ),
177                ),
178            )
179        );
180    }
181}