Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.71% covered (warning)
65.71%
253 / 385
33.33% covered (danger)
33.33%
5 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
REST_Controller
65.71% covered (warning)
65.71%
253 / 385
33.33% covered (danger)
33.33%
5 / 15
140.86
0.00% covered (danger)
0.00%
0 / 1
 register_rest_routes
96.13% covered (success)
96.13%
149 / 155
0.00% covered (danger)
0.00%
0 / 1
5
 backup_permissions_callback
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 install_backup_helper_script
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 delete_backup_helper_script
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 fetch_database_object_backup
51.85% covered (warning)
51.85%
14 / 27
0.00% covered (danger)
0.00%
0 / 1
5.79
 fetch_options_backup
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 fetch_comment_backup
17.86% covered (danger)
17.86%
5 / 28
0.00% covered (danger)
0.00%
0 / 1
7.99
 fetch_post_backup
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
2.00
 fetch_term_backup
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 fetch_user_backup
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 get_allowed_object_types
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
1
 get_site_backup_undo_event
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
132
 fetch_wc_orders_backup
33.33% covered (danger)
33.33%
8 / 24
0.00% covered (danger)
0.00%
0 / 1
21.52
 get_site_backup_preflight
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
12
 get_option_row
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * The Backup Rest Controller class.
4 * Registers the REST routes for Backup.
5 *
6 * @package automattic/jetpack-backup
7 */
8
9// After changing this file, consider increasing the version number ("VXXX") in all the files using this namespace, in
10// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
11// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
12// are installed, or in some other cases).
13namespace Automattic\Jetpack\Backup\V0005;
14
15use Automattic\Jetpack\Connection\Client;
16use Automattic\Jetpack\Connection\Rest_Authentication;
17use Automattic\Jetpack\Sync\Actions as Sync_Actions;
18use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
19use Jetpack_Options;
20use WP_Error;
21use WP_REST_Request;
22use WP_REST_Server;
23// phpcs:ignore WordPress.Utils.I18nTextDomainFixer.MissingArgs
24use function esc_html__;
25use function get_comment;
26use function get_comment_meta;
27use function get_metadata;
28use function get_post;
29use function get_post_meta;
30use function get_term;
31use function get_term_meta;
32use function get_user_by;
33use function get_user_meta;
34use function is_wp_error;
35use function register_rest_route;
36use function rest_authorization_required_code;
37use function rest_ensure_response;
38use function wp_remote_retrieve_response_code;
39
40/**
41 * Registers the REST routes for Backup.
42 */
43class REST_Controller {
44    /**
45     * Registers the REST routes for Backup.
46     *
47     * @access public
48     * @static
49     */
50    public static function register_rest_routes() {
51        // Install a Helper Script to assist Jetpack Backup fetch data.
52        register_rest_route(
53            'jetpack/v4',
54            '/backup-helper-script',
55            array(
56                'methods'             => WP_REST_Server::CREATABLE,
57                'callback'            => __CLASS__ . '::install_backup_helper_script',
58                'permission_callback' => __CLASS__ . '::backup_permissions_callback',
59                'args'                => array(
60                    'helper' => array(
61                        'description' => __( 'base64 encoded Backup Helper Script body.', 'jetpack-backup-pkg' ),
62                        'type'        => 'string',
63                        'required'    => true,
64                    ),
65                ),
66            )
67        );
68
69        // Delete a Backup Helper Script.
70        register_rest_route(
71            'jetpack/v4',
72            '/backup-helper-script',
73            array(
74                'methods'             => WP_REST_Server::DELETABLE,
75                'callback'            => __CLASS__ . '::delete_backup_helper_script',
76                'permission_callback' => __CLASS__ . '::backup_permissions_callback',
77                'args'                => array(
78                    'path' => array(
79                        'description' => __( 'Path to Backup Helper Script', 'jetpack-backup-pkg' ),
80                        'type'        => 'string',
81                        'required'    => true,
82                    ),
83                ),
84            )
85        );
86
87        // Fetch a backup of a database object, along with all of its metadata.
88        register_rest_route(
89            'jetpack/v4',
90            '/database-object/backup',
91            array(
92                'methods'             => WP_REST_Server::READABLE,
93                'callback'            => __CLASS__ . '::fetch_database_object_backup',
94                'permission_callback' => __CLASS__ . '::backup_permissions_callback',
95                'args'                => array(
96                    'object_type' => array(
97                        'description'       => __( 'Type of object to fetch from the database', 'jetpack-backup-pkg' ),
98                        'required'          => true,
99                        'validate_callback' => function ( $value ) {
100                            if ( ! is_string( $value ) ) {
101                                return new WP_Error(
102                                    'rest_invalid_param',
103                                    __( 'The object_type argument must be a non-empty string.', 'jetpack-backup-pkg' ),
104                                    array( 'status' => 400 )
105                                );
106                            }
107
108                            $allowed_object_types = array_keys( self::get_allowed_object_types() );
109
110                            if ( ! in_array( $value, $allowed_object_types, true ) ) {
111                                return new WP_Error(
112                                    'rest_invalid_param',
113                                    sprintf(
114                                        /* translators: %s: comma-separated list of allowed object types */
115                                        __( 'The object_type argument should be one of %s', 'jetpack-backup-pkg' ),
116                                        implode( ', ', $allowed_object_types )
117                                    ),
118                                    array( 'status' => 400 )
119                                );
120                            }
121
122                            return true;
123                        },
124                    ),
125                    'object_id'   => array(
126                        'description' => __( 'ID of the database object to fetch', 'jetpack-backup-pkg' ),
127                        'type'        => 'integer',
128                        'required'    => true,
129                    ),
130                ),
131            )
132        );
133
134        // Fetch a backup of an option.
135        register_rest_route(
136            'jetpack/v4',
137            '/options/backup',
138            array(
139                'methods'             => WP_REST_Server::READABLE,
140                'callback'            => __CLASS__ . '::fetch_options_backup',
141                'permission_callback' => __CLASS__ . '::backup_permissions_callback',
142                'args'                => array(
143                    'name' => array(
144                        'description'       => __( 'One or more option names to include in the backup', 'jetpack-backup-pkg' ),
145                        'validate_callback' => function ( $value ) {
146                            $is_valid = is_array( $value ) || is_string( $value );
147                            if ( ! $is_valid ) {
148                                return new WP_Error( 'rest_invalid_param', __( 'The name argument should be an option name or an array of option names', 'jetpack-backup-pkg' ), array( 'status' => 400 ) );
149                            }
150
151                            return true;
152                        },
153                        'required'          => true,
154                    ),
155                ),
156            )
157        );
158
159        // Fetch a backup of a comment, along with all of its metadata.
160        register_rest_route(
161            'jetpack/v4',
162            '/comments/(?P<id>\d+)/backup',
163            array(
164                'methods'             => WP_REST_Server::READABLE,
165                'callback'            => __CLASS__ . '::fetch_comment_backup',
166                'permission_callback' => __CLASS__ . '::backup_permissions_callback',
167            )
168        );
169
170        // Fetch a backup of a post, along with all of its metadata.
171        register_rest_route(
172            'jetpack/v4',
173            '/posts/(?P<id>\d+)/backup',
174            array(
175                'methods'             => WP_REST_Server::READABLE,
176                'callback'            => __CLASS__ . '::fetch_post_backup',
177                'permission_callback' => __CLASS__ . '::backup_permissions_callback',
178            )
179        );
180
181        // Fetch a backup of a term, along with all of its metadata.
182        register_rest_route(
183            'jetpack/v4',
184            '/terms/(?P<id>\d+)/backup',
185            array(
186                'methods'             => WP_REST_Server::READABLE,
187                'callback'            => __CLASS__ . '::fetch_term_backup',
188                'permission_callback' => __CLASS__ . '::backup_permissions_callback',
189            )
190        );
191
192        // Fetch a backup of a user, along with all of its metadata.
193        register_rest_route(
194            'jetpack/v4',
195            '/users/(?P<id>\d+)/backup',
196            array(
197                'methods'             => WP_REST_Server::READABLE,
198                'callback'            => __CLASS__ . '::fetch_user_backup',
199                'permission_callback' => __CLASS__ . '::backup_permissions_callback',
200            )
201        );
202
203        // Get backup undo event
204        register_rest_route(
205            'jetpack/v4',
206            '/site/backup/undo-event',
207            array(
208                'methods'             => WP_REST_Server::READABLE,
209                'callback'            => __CLASS__ . '::get_site_backup_undo_event',
210                'permission_callback' => __NAMESPACE__ . '\Jetpack_Backup::backups_permissions_callback',
211            )
212        );
213
214        // Fetch a backup of a wc_order along with all of its data.
215        register_rest_route(
216            'jetpack/v4',
217            '/orders/(?P<id>\d+)/backup',
218            array(
219                'methods'             => WP_REST_Server::READABLE,
220                'callback'            => __CLASS__ . '::fetch_wc_orders_backup',
221                'permission_callback' => __CLASS__ . '::backup_permissions_callback',
222            )
223        );
224
225        // Fetch backup preflight status
226        register_rest_route(
227            'jetpack/v4',
228            '/site/backup/preflight',
229            array(
230                'methods'             => WP_REST_Server::READABLE,
231                'callback'            => __CLASS__ . '::get_site_backup_preflight',
232                'permission_callback' => __NAMESPACE__ . '\Jetpack_Backup::backups_permissions_callback',
233            )
234        );
235    }
236
237    /**
238     * The Backup endpoints should only be available via site-level authentication.
239     * This means that the corresponding endpoints can only be accessible from WPCOM.
240     *
241     * @access public
242     * @static
243     *
244     * @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
245     */
246    public static function backup_permissions_callback() {
247        if ( Rest_Authentication::is_signed_with_blog_token() ) {
248            return true;
249        }
250
251        $error_msg = esc_html__(
252            'You are not allowed to perform this action.',
253            'jetpack-backup-pkg'
254        );
255
256        return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) );
257    }
258
259    /**
260     * Install the Backup Helper Script.
261     *
262     * @access public
263     * @static
264     *
265     * @param WP_REST_Request $request The request sent to the WP REST API.
266     *
267     * @return array|WP_Error Array with installation info on success:
268     *
269     *   'path'    (string) Helper script installation path on the filesystem.
270     *   'url'     (string) URL to the helper script.
271     *   'abspath' (string) WordPress root.
272     *
273     *   or an instance of WP_Error on failure.
274     */
275    public static function install_backup_helper_script( $request ) {
276        $helper_script = $request->get_param( 'helper' );
277
278        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
279        $helper_script = base64_decode( $helper_script );
280        if ( ! $helper_script ) {
281            return new WP_Error( 'invalid_args', __( 'Helper script body must be base64 encoded', 'jetpack-backup-pkg' ), 400 );
282        }
283
284        $installation_info = Helper_Script_Manager::install_helper_script( $helper_script );
285        Helper_Script_Manager::cleanup_expired_helper_scripts();
286
287        return rest_ensure_response( $installation_info );
288    }
289
290    /**
291     * Delete a Backup Helper Script.
292     *
293     * @access public
294     * @static
295     *
296     * @param WP_REST_Request $request The request sent to the WP REST API.
297     *
298     * @return array|WP_Error An array with 'success' key, or an instance of WP_Error on failure.
299     */
300    public static function delete_backup_helper_script( $request ) {
301        $path_to_helper_script = $request->get_param( 'path' );
302
303        $delete_result = Helper_Script_Manager::delete_helper_script( $path_to_helper_script );
304        Helper_Script_Manager::cleanup_expired_helper_scripts();
305
306        if ( is_wp_error( $delete_result ) ) {
307            return $delete_result;
308        }
309
310        return rest_ensure_response( array( 'success' => true ) );
311    }
312
313    /**
314     * Fetch a backup of a database object, along with all of its metadata.
315     *
316     * @access public
317     * @static
318     *
319     * @param WP_REST_Request $request The request sent to the WP REST API.
320     *
321     * @return array
322     */
323    public static function fetch_database_object_backup( $request ) {
324        global $wpdb;
325
326        // Disable Sync as this is a read-only operation and triggered by sync activity.
327        Sync_Actions::mark_sync_read_only();
328
329        $allowed_object_types = self::get_allowed_object_types();
330        // Safe to do this as we have already validated the object_type key exists in self::get_allowed_object_types().
331        $object_type = $allowed_object_types[ $request->get_param( 'object_type' ) ];
332        $object_id   = $request->get_param( 'object_id' );
333        $table       = $wpdb->prefix . $object_type['table'];
334        $id_field    = $object_type['id_field'];
335
336        // Fetch the requested object.
337        $object = $wpdb->get_row(
338            $wpdb->prepare(
339                'SELECT * FROM `' . $table . '` WHERE `' . $id_field . '` = %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
340                $object_id
341            )
342        );
343
344        if ( empty( $object ) ) {
345            return new WP_Error( 'object_not_found', __( 'Object not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
346        }
347
348        $result = array( 'object' => $object );
349
350        // Fetch associated metadata (if this object type has any).
351        if ( ! empty( $object_type['meta_type'] ) ) {
352            $result['meta'] = get_metadata( $object_type['meta_type'], $object_id );
353        }
354
355        // If there is a child linked table (eg: woocommerce_tax_rate_locations), fetch linked records.
356        if ( ! empty( $object_type['child_table'] ) ) {
357            $child_table    = $wpdb->prefix . $object_type['child_table'];
358            $child_id_field = $object_type['child_id_field'];
359
360            $result['children'] = $wpdb->get_results(
361                $wpdb->prepare(
362                    'SELECT * FROM `' . $child_table . '` where `' . $child_id_field . '` = %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
363                    $object_id
364                )
365            );
366        }
367
368        return $result;
369    }
370
371    /**
372     * Fetch a backup of an option.
373     *
374     * @access public
375     * @static
376     *
377     * @param WP_REST_Request $request The request sent to the WP REST API.
378     *
379     * @return array
380     */
381    public static function fetch_options_backup( $request ) {
382        // Disable Sync as this is a read-only operation and triggered by sync activity.
383        Sync_Actions::mark_sync_read_only();
384
385        $option_names = (array) $request->get_param( 'name' );
386
387        $options = array_map( self::class . '::get_option_row', $option_names );
388        return array( 'options' => $options );
389    }
390
391    /**
392     * Fetch a backup of a comment, along with all of its metadata.
393     *
394     * @access public
395     * @static
396     *
397     * @param WP_REST_Request $request The request sent to the WP REST API.
398     *
399     * @return array
400     */
401    public static function fetch_comment_backup( $request ) {
402        // Disable Sync as this is a read-only operation and triggered by sync activity.
403        Sync_Actions::mark_sync_read_only();
404
405        $comment_id = $request['id'];
406        $comment    = get_comment( $comment_id );
407
408        if ( empty( $comment ) ) {
409            return new WP_Error( 'comment_not_found', __( 'Comment not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
410        }
411
412        $allowed_keys = array(
413            'comment_ID',
414            'comment_post_ID',
415            'comment_author',
416            'comment_author_email',
417            'comment_author_url',
418            'comment_author_IP',
419            'comment_date',
420            'comment_date_gmt',
421            'comment_content',
422            'comment_karma',
423            'comment_approved',
424            'comment_agent',
425            'comment_type',
426            'comment_parent',
427            'user_id',
428        );
429
430        $comment = array_intersect_key( $comment->to_array(), array_flip( $allowed_keys ) );
431
432        $comment_meta = get_comment_meta( $comment['comment_ID'] );
433
434        return array(
435            'comment' => $comment,
436            'meta'    => is_array( $comment_meta ) ? $comment_meta : array(),
437        );
438    }
439
440    /**
441     * Fetch a backup of a post, along with all of its metadata.
442     *
443     * @access public
444     * @static
445     *
446     * @param WP_REST_Request $request The request sent to the WP REST API.
447     *
448     * @return array
449     */
450    public static function fetch_post_backup( $request ) {
451        global $wpdb;
452
453        // Disable Sync as this is a read-only operation and triggered by sync activity.
454        Sync_Actions::mark_sync_read_only();
455
456        $post_id = $request['id'];
457        $post    = get_post( $post_id );
458
459        if ( empty( $post ) ) {
460            return new WP_Error( 'post_not_found', __( 'Post not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
461        }
462
463        // Fetch terms associated with this post object.
464        $terms = $wpdb->get_results(
465            $wpdb->prepare(
466                "SELECT term_taxonomy_id, term_order FROM {$wpdb->term_relationships} WHERE object_id = %d;",
467                $post->ID
468            )
469        );
470
471        return array(
472            'post'  => (array) $post,
473            'meta'  => get_post_meta( $post->ID ),
474            'terms' => (array) $terms,
475        );
476    }
477
478    /**
479     * Fetch a backup of a term, along with all of its metadata.
480     *
481     * @access public
482     * @static
483     *
484     * @param WP_REST_Request $request The request sent to the WP REST API.
485     *
486     * @return array
487     */
488    public static function fetch_term_backup( $request ) {
489        // Disable Sync as this is a read-only operation and triggered by sync activity.
490        Sync_Actions::mark_sync_read_only();
491
492        $term_id = $request['id'];
493        $term    = get_term( $term_id );
494
495        if ( empty( $term ) ) {
496            return new WP_Error( 'term_not_found', __( 'Term not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
497        }
498
499        return array(
500            'term' => (array) $term,
501            'meta' => get_term_meta( $term_id ),
502        );
503    }
504
505    /**
506     * Fetch a backup of a user, along with all of its metadata.
507     *
508     * @access public
509     * @static
510     *
511     * @param WP_REST_Request $request The request sent to the WP REST API.
512     * @return array
513     */
514    public static function fetch_user_backup( $request ) {
515        // Disable Sync as this is a read-only operation and triggered by sync activity.
516        Sync_Actions::mark_sync_read_only();
517
518        $user_id = $request['id'];
519        $user    = get_user_by( 'id', $user_id );
520
521        if ( empty( $user ) ) {
522            return new WP_Error( 'user_not_found', __( 'User not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
523        }
524
525        return array(
526            'user' => $user->to_array(),
527            'meta' => get_user_meta( $user->ID ),
528        );
529    }
530
531    /**
532     * Get allowed object types for the '/database-object/backup' endpoint.
533     *
534     * @access private
535     * @static
536     *
537     * @return array
538     */
539    private static function get_allowed_object_types() {
540        return array(
541            'woocommerce_attribute'                       => array(
542                'table'    => 'woocommerce_attribute_taxonomies',
543                'id_field' => 'attribute_id',
544            ),
545            'woocommerce_downloadable_product_permission' => array(
546                'table'    => 'woocommerce_downloadable_product_permissions',
547                'id_field' => 'permission_id',
548            ),
549            'woocommerce_order_item'                      => array(
550                'table'     => 'woocommerce_order_items',
551                'id_field'  => 'order_item_id',
552                'meta_type' => 'order_item',
553            ),
554            'woocommerce_payment_token'                   => array(
555                'table'     => 'woocommerce_payment_tokens',
556                'id_field'  => 'token_id',
557                'meta_type' => 'payment_token',
558            ),
559            'woocommerce_tax_rate'                        => array(
560                'table'          => 'woocommerce_tax_rates',
561                'id_field'       => 'tax_rate_id',
562                'child_table'    => 'woocommerce_tax_rate_locations',
563                'child_id_field' => 'tax_rate_id',
564            ),
565            'woocommerce_webhook'                         => array(
566                'table'    => 'wc_webhooks',
567                'id_field' => 'webhook_id',
568            ),
569        );
570    }
571
572    /**
573     * This will fetch the last rewindable event from the Activity Log and
574     * the last rewind_id prior to that.
575     */
576    public static function get_site_backup_undo_event() {
577        $blog_id = Jetpack_Options::get_option( 'id' );
578
579        $response = Client::wpcom_json_api_request_as_user(
580            '/sites/' . $blog_id . '/activity?force=wpcom',
581            'v2',
582            array(),
583            null,
584            'wpcom'
585        );
586
587        if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
588            return null;
589        }
590
591        $body = json_decode( $response['body'], true );
592
593        if ( ! isset( $body['current'] ) ) {
594            return null;
595        }
596
597        if ( ! isset( $body['current']['orderedItems'] ) ) {
598            return null;
599        }
600
601        // Preparing the response structure
602        $undo_event = array(
603            'last_rewindable_event' => null,
604            'undo_backup_id'        => null,
605        );
606
607        // List of events that will not be considered to be undo.
608        // Basically we should not `undo` a full backup event, but we could
609        // use them to undo any other action like plugin updates.
610        $last_event_exceptions = array(
611            'rewind__backup_only_complete_full',
612            'rewind__backup_only_complete_initial',
613            'rewind__backup_only_complete',
614            'rewind__backup_complete_full',
615            'rewind__backup_complete_initial',
616            'rewind__backup_complete',
617        );
618
619        // Looping through the events to find the last rewindable event and the last backup_id.
620        // The idea is to find the last rewindable event and then the last rewind_id before that.
621        $found_last_event = false;
622        foreach ( $body['current']['orderedItems'] as $event ) {
623            if ( $event['is_rewindable'] ) {
624                if ( ! $found_last_event && ! in_array( $event['name'], $last_event_exceptions, true ) ) {
625                    $undo_event['last_rewindable_event'] = $event;
626                    $found_last_event                    = true;
627                } elseif ( $found_last_event ) {
628                    $undo_event['undo_backup_id'] = $event['rewind_id'];
629                    break;
630                }
631            }
632        }
633
634        // Ensure that we have a rewindable event and a backup_id to undo.
635        if ( $undo_event['last_rewindable_event'] === null || $undo_event['undo_backup_id'] === null ) {
636            return null;
637        }
638
639        return rest_ensure_response( $undo_event );
640    }
641
642    /**
643     * Fetch a backup of a order, along with all of its data.
644     *
645     * @access public
646     * @static
647     *
648     * @param WP_REST_Request $request The request sent to the WP REST API.
649     *
650     * @return array
651     */
652    public static function fetch_wc_orders_backup( $request ) {
653        global $wpdb;
654
655        // Disable Sync as this is a read-only operation and triggered by sync activity.
656        Sync_Actions::mark_sync_read_only();
657
658        $order_id = $request['id'];
659
660        $order                  = array();
661        $order_addresses        = array();
662        $order_operational_data = array();
663        $order_meta             = array();
664
665        if ( ! class_exists( OrdersTableDataStore::class ) ) {
666            return new WP_Error( 'order_not_allowed', __( 'Not allowed to get the order with current configuration', 'jetpack-backup-pkg' ), array( 'status' => 403 ) );
667        }
668
669        if ( method_exists( OrdersTableDataStore::class, 'get_orders_table_name' ) ) {
670            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
671            $order = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM `' . OrdersTableDataStore::get_orders_table_name() . '` WHERE id = %s', $order_id ) );
672        }
673
674        if ( empty( $order ) ) {
675            // No order in HPOS
676            return new WP_Error( 'order_not_found', __( 'Order not found ', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
677        }
678
679        if ( method_exists( OrdersTableDataStore::class, 'get_addresses_table_name' ) ) {
680            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
681            $order_addresses = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM `' . OrdersTableDataStore::get_addresses_table_name() . '` WHERE order_id = %s', $order_id ) );
682        }
683
684        if ( method_exists( OrdersTableDataStore::class, 'get_operational_data_table_name' ) ) {
685            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
686            $order_operational_data = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM `' . OrdersTableDataStore::get_operational_data_table_name() . '` WHERE order_id = %s', $order_id ) );
687        }
688
689        if ( method_exists( OrdersTableDataStore::class, 'get_meta_table_name' ) ) {
690            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
691            $order_meta = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM `' . OrdersTableDataStore::get_meta_table_name() . '` WHERE order_id = %s', $order_id ) );
692        }
693
694        return array(
695            'order'                  => (array) $order,
696            'order_addresses'        => (array) $order_addresses,
697            'order_operational_data' => (array) $order_operational_data,
698            'order_meta'             => (array) $order_meta,
699        );
700    }
701
702    /**
703     * Fetch backup preflight status
704     *
705     * @return array
706     */
707    public static function get_site_backup_preflight() {
708        $blog_id = Jetpack_Options::get_option( 'id' );
709
710        $response = Client::wpcom_json_api_request_as_user(
711            '/sites/' . $blog_id . '/rewind/preflight?force=wpcom',
712            'v2',
713            array(),
714            null,
715            'wpcom'
716        );
717
718        if ( is_wp_error( $response ) ) {
719            return new WP_Error(
720                'wp_error_fetch_preflight',
721                $response->get_error_message(),
722                array( 'status' => 500 )
723            );
724        }
725
726        $response_code = wp_remote_retrieve_response_code( $response );
727        if ( 200 !== $response_code ) {
728            return new WP_Error(
729                'http_error_fetch_preflight',
730                wp_remote_retrieve_response_message( $response ),
731                array( 'status' => $response_code )
732            );
733        }
734
735        $body = json_decode( $response['body'], true );
736        return rest_ensure_response( $body );
737    }
738
739    /**
740     * Fetch option row by option name.
741     *
742     * @access private
743     * @static
744     *
745     * @param string $name The option name.
746     * @return object|null Database query result as object format specified or null on failure.
747     */
748    private static function get_option_row( $name ) {
749        global $wpdb;
750        return $wpdb->get_row( $wpdb->prepare( "select * from `{$wpdb->options}` where option_name = %s", $name ) );
751    }
752}