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