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