PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
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 13.7.2 All 507 releases
← All changes | jetpack_vendor/automattic/jetpack-backup/src/class-rest-controller.php +197 -3 13.1.5 → 16.3-beta View file →
@@ -9,18 +9,18 @@
9 9 // After changing this file, consider increasing the version number ("VXXX") in all the files using this namespace, in
10 10 // order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
11 11 // to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
12 12 // are installed, or in some other cases).
13 -namespace Automattic\Jetpack\Backup\V0002;
13 +namespace Automattic\Jetpack\Backup\V0005;
14 14
15 15 use Automattic\Jetpack\Connection\Client;
16 16 use Automattic\Jetpack\Connection\Rest_Authentication;
17 17 use Automattic\Jetpack\Sync\Actions as Sync_Actions;
18 +use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
18 19 use Jetpack_Options;
19 20 use WP_Error;
20 21 use WP_REST_Request;
21 22 use WP_REST_Server;
22 -// phpcs:ignore WordPress.Utils.I18nTextDomainFixer.MissingArgs
23 23 use function esc_html__;
24 24 use function get_comment;
25 25 use function get_comment_meta;
26 26 use function get_metadata;
@@ -33,9 +33,11 @@
33 33 use function is_wp_error;
34 34 use function register_rest_route;
35 35 use function rest_authorization_required_code;
36 36 use function rest_ensure_response;
37 +use function wp_cache_flush;
37 38 use function wp_remote_retrieve_response_code;
39 +use function wp_using_ext_object_cache;
38 40
39 41 /**
40 42 * Registers the REST routes for Backup.
41 43 */
@@ -208,8 +210,41 @@
208 210 'callback' => __CLASS__ . '::get_site_backup_undo_event',
209 211 'permission_callback' => __NAMESPACE__ . '\Jetpack_Backup::backups_permissions_callback',
210 212 )
211 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 + );
212 247 }
213 248
214 249 /**
215 250 * The Backup endpoints should only be available via site-level authentication.
@@ -560,9 +595,13 @@
560 595 null,
561 596 'wpcom'
562 597 );
563 598
564 - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
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 ) ) {
565 604 return null;
566 605 }
567 606
568 607 $body = json_decode( $response['body'], true );
@@ -613,8 +652,163 @@
613 652 return null;
614 653 }
615 654
616 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 ) );
617 811 }
618 812
619 813 /**
620 814 * Fetch option row by option name.