PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
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 13.8.3 All 506 releases
← All changes | jetpack_vendor/automattic/jetpack-backup/src/class-rest-controller.php +242 -23 12.8.3 → 16.3-a.7 View file →
@@ -5,15 +5,39 @@
5 5 *
6 6 * @package automattic/jetpack-backup
7 7 */
8 8
9 -namespace Automattic\Jetpack\Backup;
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;
10 14
15 +use Automattic\Jetpack\Connection\Client;
11 16 use Automattic\Jetpack\Connection\Rest_Authentication;
12 17 use Automattic\Jetpack\Sync\Actions as Sync_Actions;
18 +use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
19 +use Jetpack_Options;
13 20 use WP_Error;
14 21 use WP_REST_Request;
15 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;
16 40
17 41 /**
18 42 * Registers the REST routes for Backup.
19 43 */
@@ -183,11 +207,44 @@
183 207 '/site/backup/undo-event',
184 208 array(
185 209 'methods' => WP_REST_Server::READABLE,
186 210 'callback' => __CLASS__ . '::get_site_backup_undo_event',
187 - 'permission_callback' => '\Jetpack_Backup::backups_permissions_callback',
211 + 'permission_callback' => __NAMESPACE__ . '\Jetpack_Backup::backups_permissions_callback',
188 212 )
189 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 + );
190 247 }
191 248
192 249 /**
193 250 * The Backup endpoints should only be available via site-level authentication.
@@ -217,14 +274,16 @@
217 274 * @access public
218 275 * @static
219 276 *
220 277 * @param WP_REST_Request $request The request sent to the WP REST API.
221 - * @return array|WP_Error Returns the result of Helper Script installation. Returns one of:
222 - * - WP_Error on failure, or
223 - * - An array with installation info on success:
224 - * 'path' (string) The sinstallation path.
225 - * 'url' (string) The access url.
226 - * 'abspath' (string) The abspath.
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.
227 286 */
228 287 public static function install_backup_helper_script( $request ) {
229 288 $helper_script = $request->get_param( 'helper' );
230 289
@@ -236,13 +295,8 @@
236 295
237 296 $installation_info = Helper_Script_Manager::install_helper_script( $helper_script );
238 297 Helper_Script_Manager::cleanup_expired_helper_scripts();
239 298
240 - // Include ABSPATH with successful result.
241 - if ( ! is_wp_error( $installation_info ) ) {
242 - $installation_info['abspath'] = ABSPATH;
243 - }
244 -
245 299 return rest_ensure_response( $installation_info );
246 300 }
247 301
248 302 /**
@@ -251,21 +305,22 @@
251 305 * @access public
252 306 * @static
253 307 *
254 308 * @param WP_REST_Request $request The request sent to the WP REST API.
255 - * @return array An array with 'success' key indicating the result of the delete operation.
309 + *
310 + * @return array|WP_Error An array with 'success' key, or an instance of WP_Error on failure.
256 311 */
257 312 public static function delete_backup_helper_script( $request ) {
258 313 $path_to_helper_script = $request->get_param( 'path' );
259 314
260 - $deleted = Helper_Script_Manager::delete_helper_script( $path_to_helper_script );
315 + $delete_result = Helper_Script_Manager::delete_helper_script( $path_to_helper_script );
261 316 Helper_Script_Manager::cleanup_expired_helper_scripts();
262 317
263 - return rest_ensure_response(
264 - array(
265 - 'success' => $deleted,
266 - )
267 - );
318 + if ( is_wp_error( $delete_result ) ) {
319 + return $delete_result;
320 + }
321 +
322 + return rest_ensure_response( array( 'success' => true ) );
268 323 }
269 324
270 325 /**
271 326 * Fetch a backup of a database object, along with all of its metadata.
@@ -273,8 +328,9 @@
273 328 * @access public
274 329 * @static
275 330 *
276 331 * @param WP_REST_Request $request The request sent to the WP REST API.
332 + *
277 333 * @return array
278 334 */
279 335 public static function fetch_database_object_backup( $request ) {
280 336 global $wpdb;
@@ -330,8 +386,9 @@
330 386 * @access public
331 387 * @static
332 388 *
333 389 * @param WP_REST_Request $request The request sent to the WP REST API.
390 + *
334 391 * @return array
335 392 */
336 393 public static function fetch_options_backup( $request ) {
337 394 // Disable Sync as this is a read-only operation and triggered by sync activity.
@@ -349,8 +406,9 @@
349 406 * @access public
350 407 * @static
351 408 *
352 409 * @param WP_REST_Request $request The request sent to the WP REST API.
410 + *
353 411 * @return array
354 412 */
355 413 public static function fetch_comment_backup( $request ) {
356 414 // Disable Sync as this is a read-only operation and triggered by sync activity.
@@ -397,8 +455,9 @@
397 455 * @access public
398 456 * @static
399 457 *
400 458 * @param WP_REST_Request $request The request sent to the WP REST API.
459 + *
401 460 * @return array
402 461 */
403 462 public static function fetch_post_backup( $request ) {
404 463 global $wpdb;
@@ -434,8 +493,9 @@
434 493 * @access public
435 494 * @static
436 495 *
437 496 * @param WP_REST_Request $request The request sent to the WP REST API.
497 + *
438 498 * @return array
439 499 */
440 500 public static function fetch_term_backup( $request ) {
441 501 // Disable Sync as this is a read-only operation and triggered by sync activity.
@@ -525,11 +585,11 @@
525 585 * This will fetch the last rewindable event from the Activity Log and
526 586 * the last rewind_id prior to that.
527 587 */
528 588 public static function get_site_backup_undo_event() {
529 - $blog_id = \Jetpack_Options::get_option( 'id' );
589 + $blog_id = Jetpack_Options::get_option( 'id' );
530 590
531 - $response = \Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_user(
591 + $response = Client::wpcom_json_api_request_as_user(
532 592 '/sites/' . $blog_id . '/activity?force=wpcom',
533 593 'v2',
534 594 array(),
535 595 null,
@@ -535,9 +595,13 @@
535 595 null,
536 596 'wpcom'
537 597 );
538 598
539 - 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 ) ) {
540 604 return null;
541 605 }
542 606
543 607 $body = json_decode( $response['body'], true );
@@ -588,8 +652,163 @@
588 652 return null;
589 653 }
590 654
591 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 ) );
592 811 }
593 812
594 813 /**
595 814 * Fetch option row by option name.