PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta.3
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta.3
16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-backup / src / class-rest-controller.php
jetpack / jetpack_vendor / automattic / jetpack-backup / src Last commit date
abilities 2 months ago class-initial-state.php 8 months ago class-jetpack-backup-upgrades.php 1 year ago class-jetpack-backup.php 3 weeks ago class-package-version-compat.php 2 years ago class-package-version.php 3 weeks ago class-rest-controller.php 2 months ago
class-rest-controller.php
752 lines
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_remote_retrieve_response_code;
38
39 /**
40 * Registers the REST routes for Backup.
41 */
42 class 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 }
752