Completed
Push — update/backup-pkg-real-time-ba... ( 182945...8e36b9 )
by
unknown
40:09 queued 29:02
created

REST_Controller::get_option_row()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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
namespace Automattic\Jetpack\Backup;
10
11
use Automattic\Jetpack\Connection\Rest_Authentication;
12
use Automattic\Jetpack\Sync\Actions as Sync_Actions;
13
use WP_Error;
14
use WP_REST_Request;
15
use WP_REST_Server;
16
17
/**
18
 * Registers the REST routes for Backup.
19
 */
20
class REST_Controller {
21
	/**
22
	 * Registers the REST routes for Backup.
23
	 *
24
	 * @access public
25
	 * @static
26
	 */
27
	public static function register_rest_routes() {
28
		// Install a Helper Script to assist Jetpack Backup fetch data.
29
		register_rest_route(
30
			'jetpack/v4',
31
			'/backup-helper-script',
32
			array(
33
				'methods'             => WP_REST_Server::CREATABLE,
34
				'callback'            => __CLASS__ . '::install_backup_helper_script',
35
				'permission_callback' => __CLASS__ . '::backup_permissions_callback',
36
				'args'                => array(
37
					'helper' => array(
38
						'description' => __( 'base64 encoded Backup Helper Script body.', 'jetpack' ),
39
						'type'        => 'string',
40
						'required'    => true,
41
					),
42
				),
43
			)
44
		);
45
46
		// Delete a Backup Helper Script.
47
		register_rest_route(
48
			'jetpack/v4',
49
			'/backup-helper-script',
50
			array(
51
				'methods'             => WP_REST_Server::DELETABLE,
52
				'callback'            => __CLASS__ . '::delete_backup_helper_script',
53
				'permission_callback' => __CLASS__ . '::backup_permissions_callback',
54
				'args'                => array(
55
					'path' => array(
56
						'description' => __( 'Path to Backup Helper Script', 'jetpack' ),
57
						'type'        => 'string',
58
						'required'    => true,
59
					),
60
				),
61
			)
62
		);
63
64
		// Fetch a backup of a database object, along with all of its metadata.
65
		register_rest_route(
66
			'jetpack/v4',
67
			'/database-object/backup',
68
			array(
69
				'methods'             => WP_REST_Server::READABLE,
70
				'callback'            => __CLASS__ . '::fetch_database_object_backup',
71
				'permission_callback' => __CLASS__ . '::backup_permissions_callback',
72
				'args'                => array(
73
					'object_type' => array(
74
						'description'       => __( 'Type of object to fetch from the database', 'jetpack' ),
75
						'required'          => true,
76
						'validate_callback' => function ( $value ) {
77
							if ( ! is_string( $value ) ) {
78
								return new WP_Error(
79
									'rest_invalid_param',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'rest_invalid_param'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
80
									__( 'The object_type argument must be a non-empty string.', 'jetpack' ),
81
									array( 'status' => 400 )
82
								);
83
							}
84
85
							$allowed_object_types = array_keys( self::get_allowed_object_types() );
86
87 View Code Duplication
							if ( ! in_array( $value, $allowed_object_types, true ) ) {
88
								return new WP_Error(
89
									'rest_invalid_param',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'rest_invalid_param'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
90
									sprintf(
91
										/* translators: %s: comma-separated list of allowed object types */
92
										__( 'The object_type argument should be one of %s', 'jetpack' ),
93
										implode( ', ', $allowed_object_types )
94
									),
95
									array( 'status' => 400 )
96
								);
97
							}
98
99
							return true;
100
						},
101
					),
102
					'object_id'   => array(
103
						'description' => __( 'ID of the database object to fetch', 'jetpack' ),
104
						'type'        => 'integer',
105
						'required'    => true,
106
					),
107
				),
108
			)
109
		);
110
111
		// Fetch a backup of an option.
112
		register_rest_route(
113
			'jetpack/v4',
114
			'/options/backup',
115
			array(
116
				'methods'             => WP_REST_Server::READABLE,
117
				'callback'            => __CLASS__ . '::fetch_options_backup',
118
				'permission_callback' => __CLASS__ . '::backup_permissions_callback',
119
				'args'                => array(
120
					'name' => array(
121
						'description'       => __( 'One or more option names to include in the backup', 'jetpack' ),
122
						'validate_callback' => function ( $value ) {
123
							$is_valid = is_array( $value ) || is_string( $value );
124
							if ( ! $is_valid ) {
125
								return new WP_Error( 'rest_invalid_param', __( 'The name argument should be an option name or an array of option names', 'jetpack' ), array( 'status' => 400 ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'rest_invalid_param'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
126
							}
127
128
							return true;
129
						},
130
						'required'          => true,
131
					),
132
				),
133
			)
134
		);
135
136
		// Fetch a backup of a comment, along with all of its metadata.
137
		register_rest_route(
138
			'jetpack/v4',
139
			'/comments/(?P<id>\d+)/backup',
140
			array(
141
				'methods'             => WP_REST_Server::READABLE,
142
				'callback'            => __CLASS__ . '::fetch_comment_backup',
143
				'permission_callback' => __CLASS__ . '::backup_permissions_callback',
144
			)
145
		);
146
147
		// Fetch a backup of a post, along with all of its metadata.
148
		register_rest_route(
149
			'jetpack/v4',
150
			'/posts/(?P<id>\d+)/backup',
151
			array(
152
				'methods'             => WP_REST_Server::READABLE,
153
				'callback'            => __CLASS__ . '::fetch_post_backup',
154
				'permission_callback' => __CLASS__ . '::backup_permissions_callback',
155
			)
156
		);
157
158
		// Fetch a backup of a term, along with all of its metadata.
159
		register_rest_route(
160
			'jetpack/v4',
161
			'/terms/(?P<id>\d+)/backup',
162
			array(
163
				'methods'             => WP_REST_Server::READABLE,
164
				'callback'            => __CLASS__ . '::fetch_term_backup',
165
				'permission_callback' => __CLASS__ . '::backup_permissions_callback',
166
			)
167
		);
168
169
		// Fetch a backup of a user, along with all of its metadata.
170
		register_rest_route(
171
			'jetpack/v4',
172
			'/users/(?P<id>\d+)/backup',
173
			array(
174
				'methods'             => WP_REST_Server::READABLE,
175
				'callback'            => __CLASS__ . '::fetch_user_backup',
176
				'permission_callback' => __CLASS__ . '::backup_permissions_callback',
177
			)
178
		);
179
	}
180
181
	/**
182
	 * The Backup endpoints should only be available via site-level authentication.
183
	 * This means that the corresponding endpoints can only be accessible from WPCOM.
184
	 *
185
	 * @access public
186
	 * @static
187
	 *
188
	 * @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
189
	 */
190 View Code Duplication
	public static function backup_permissions_callback() {
191
		if ( Rest_Authentication::is_signed_with_blog_token() ) {
192
			return true;
193
		}
194
195
		$error_msg = esc_html__(
196
			'You are not allowed to perform this action.',
197
			'jetpack'
198
		);
199
200
		return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'rest_forbidden'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
201
	}
202
203
	/**
204
	 * Install the Backup Helper Script.
205
	 *
206
	 * @access public
207
	 * @static
208
	 *
209
	 * @param WP_REST_Request $request The request sent to the WP REST API.
210
	 * @return array|WP_Error Returns the result of Helper Script installation. Returns one of:
211
	 * - WP_Error on failure, or
212
	 * - An array with installation info on success:
213
	 *  'path'    (string) The sinstallation path.
214
	 *  'url'     (string) The access url.
215
	 *  'abspath' (string) The abspath.
216
	 */
217
	public static function install_backup_helper_script( $request ) {
218
		$helper_script = $request->get_param( 'helper' );
219
220
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
221
		$helper_script = base64_decode( $helper_script );
222
		if ( ! $helper_script ) {
223
			return new WP_Error( 'invalid_args', __( 'Helper script body must be base64 encoded', 'jetpack' ), 400 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_args'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
224
		}
225
226
		$installation_info = Helper_Script_Manager::install_helper_script( $helper_script );
227
		Helper_Script_Manager::cleanup_expired_helper_scripts();
228
229
		// Include ABSPATH with successful result.
230
		if ( ! is_wp_error( $installation_info ) ) {
231
			$installation_info['abspath'] = ABSPATH;
232
		}
233
234
		return rest_ensure_response( $installation_info );
235
	}
236
237
	/**
238
	 * Delete a Backup Helper Script.
239
	 *
240
	 * @access public
241
	 * @static
242
	 *
243
	 * @param WP_REST_Request $request The request sent to the WP REST API.
244
	 * @return array An array with 'success' key indicating the result of the delete operation.
245
	 */
246
	public static function delete_backup_helper_script( $request ) {
247
		$path_to_helper_script = $request->get_param( 'path' );
248
249
		$deleted = Helper_Script_Manager::delete_helper_script( $path_to_helper_script );
250
		Helper_Script_Manager::cleanup_expired_helper_scripts();
251
252
		return rest_ensure_response(
253
			array(
254
				'success' => $deleted,
255
			)
256
		);
257
	}
258
259
	/**
260
	 * Fetch a backup of a database object, along with all of its metadata.
261
	 *
262
	 * @access public
263
	 * @static
264
	 *
265
	 * @param WP_REST_Request $request The request sent to the WP REST API.
266
	 * @return array
267
	 */
268
	public static function fetch_database_object_backup( $request ) {
269
		global $wpdb;
270
271
		// Disable Sync as this is a read-only operation and triggered by sync activity.
272
		Sync_Actions::mark_sync_read_only();
273
274
		$allowed_object_types = self::get_allowed_object_types();
275
		// Safe to do this as we have already validated the object_type key exists in self::get_allowed_object_types().
276
		$object_type = $allowed_object_types[ $request->get_param( 'object_type' ) ];
277
		$object_id   = $request->get_param( 'object_id' );
278
		$table       = $wpdb->prefix . $object_type['table'];
279
		$id_field    = $object_type['id_field'];
280
281
		// Fetch the requested object.
282
		$object = $wpdb->get_row(
283
			$wpdb->prepare(
284
				'select * from `%s` where `%s` = %d',
285
				$table,
286
				$id_field,
287
				$object_id
288
			)
289
		);
290
291
		if ( empty( $object ) ) {
292
			return new WP_Error( 'object_not_found', __( 'Object not found', 'jetpack' ), array( 'status' => 404 ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'object_not_found'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
293
		}
294
295
		$result = array( 'object' => $object );
296
297
		// Fetch associated metadata (if this object type has any).
298
		if ( ! empty( $object_type['meta_type'] ) ) {
299
			$result['meta'] = get_metadata( $object_type['meta_type'], $object_id );
300
		}
301
302
		// If there is a child linked table (eg: woocommerce_tax_rate_locations), fetch linked records.
303
		if ( ! empty( $object_type['child_table'] ) ) {
304
			$child_table    = $wpdb->prefix . $object_type['child_table'];
305
			$child_id_field = $object_type['child_id_field'];
306
307
			$result['children'] = $wpdb->get_results(
308
				$wpdb->prepare(
309
					'select * from `%s` where `%s` = %d',
310
					$child_table,
311
					$child_id_field,
312
					$object_id
313
				)
314
			);
315
		}
316
317
		return $result;
318
	}
319
320
	/**
321
	 * Fetch a backup of an option.
322
	 *
323
	 * @access public
324
	 * @static
325
	 *
326
	 * @param WP_REST_Request $request The request sent to the WP REST API.
327
	 * @return array
328
	 */
329
	public static function fetch_options_backup( $request ) {
330
		// Disable Sync as this is a read-only operation and triggered by sync activity.
331
		Sync_Actions::mark_sync_read_only();
332
333
		$option_name = $request->get_param( 'name' );
334
		if ( is_array( $option_name ) ) {
335
			$option_names = $option_name;
336
		} else {
337
			$option_names = array( $option_name );
338
		}
339
340
		$options = array_map( 'self::get_option_row', $option_names );
341
		return array( 'options' => $options );
342
	}
343
344
	/**
345
	 * Fetch a backup of a comment, along with all of its metadata.
346
	 *
347
	 * @access public
348
	 * @static
349
	 *
350
	 * @param WP_REST_Request $request The request sent to the WP REST API.
351
	 * @return array
352
	 */
353
	public static function fetch_comment_backup( $request ) {
354
		// Disable Sync as this is a read-only operation and triggered by sync activity.
355
		Sync_Actions::mark_sync_read_only();
356
357
		$comment_id = $request['id'];
358
		$comment    = get_comment( $comment_id );
359
360
		if ( empty( $comment ) ) {
361
			return new WP_Error( 'comment_not_found', __( 'Comment not found', 'jetpack' ), array( 'status' => 404 ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'comment_not_found'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
362
		}
363
364
		$allowed_keys = array(
365
			'comment_ID',
366
			'comment_post_ID',
367
			'comment_author',
368
			'comment_author_email',
369
			'comment_author_url',
370
			'comment_author_IP',
371
			'comment_date',
372
			'comment_date_gmt',
373
			'comment_content',
374
			'comment_karma',
375
			'comment_approved',
376
			'comment_agent',
377
			'comment_type',
378
			'comment_parent',
379
			'user_id',
380
		);
381
382
		$comment = array_intersect_key( $comment->to_array(), array_flip( $allowed_keys ) );
383
384
		$comment_meta = get_comment_meta( $comment['comment_ID'] );
385
386
		return array(
387
			'comment' => $comment,
388
			'meta'    => is_array( $comment_meta ) ? $comment_meta : array(),
389
		);
390
	}
391
392
	/**
393
	 * Fetch a backup of a post, along with all of its metadata.
394
	 *
395
	 * @access public
396
	 * @static
397
	 *
398
	 * @param WP_REST_Request $request The request sent to the WP REST API.
399
	 * @return array
400
	 */
401
	public static function fetch_post_backup( $request ) {
402
		global $wpdb;
403
404
		// Disable Sync as this is a read-only operation and triggered by sync activity.
405
		Sync_Actions::mark_sync_read_only();
406
407
		$post_id = $request['id'];
408
		$post    = get_post( $post_id );
409
410
		if ( empty( $post ) ) {
411
			return new WP_Error( 'post_not_found', __( 'Post not found', 'jetpack' ), array( 'status' => 404 ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'post_not_found'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
412
		}
413
414
		// Fetch terms associated with this post object.
415
		$terms = $wpdb->get_results(
416
			$wpdb->prepare(
417
				"SELECT term_taxonomy_id, term_order FROM {$wpdb->term_relationships} WHERE object_id = %d;",
418
				$post->ID
419
			)
420
		);
421
422
		return array(
423
			'post'  => (array) $post,
424
			'meta'  => get_post_meta( $post->ID ),
425
			'terms' => (array) $terms,
426
		);
427
	}
428
429
	/**
430
	 * Fetch a backup of a term, along with all of its metadata.
431
	 *
432
	 * @access public
433
	 * @static
434
	 *
435
	 * @param WP_REST_Request $request The request sent to the WP REST API.
436
	 * @return array
437
	 */
438 View Code Duplication
	public static function fetch_term_backup( $request ) {
439
		// Disable Sync as this is a read-only operation and triggered by sync activity.
440
		Sync_Actions::mark_sync_read_only();
441
442
		$term_id = $request['id'];
443
		$term    = get_term( $term_id );
444
445
		if ( empty( $term ) ) {
446
			return new WP_Error( 'term_not_found', __( 'Term not found', 'jetpack' ), array( 'status' => 404 ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'term_not_found'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
447
		}
448
449
		return array(
450
			'term' => (array) $term,
451
			'meta' => get_term_meta( $term_id ),
452
		);
453
	}
454
455
	/**
456
	 * Fetch a backup of a user, along with all of its metadata.
457
	 *
458
	 * @access public
459
	 * @static
460
	 *
461
	 * @param WP_REST_Request $request The request sent to the WP REST API.
462
	 * @return array
463
	 */
464 View Code Duplication
	public static function fetch_user_backup( $request ) {
465
		// Disable Sync as this is a read-only operation and triggered by sync activity.
466
		Sync_Actions::mark_sync_read_only();
467
468
		$user_id = $request['id'];
469
		$user    = get_user_by( 'id', $user_id );
470
471
		if ( empty( $user ) ) {
472
			return new WP_Error( 'user_not_found', __( 'User not found', 'jetpack' ), array( 'status' => 404 ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'user_not_found'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
473
		}
474
475
		return array(
476
			'user' => $user->to_array(),
477
			'meta' => get_user_meta( $user->ID ),
478
		);
479
	}
480
481
	/**
482
	 * Get allowed object types for the '/database-object/backup' endpoint.
483
	 *
484
	 * @access private
485
	 * @static
486
	 *
487
	 * @return array
488
	 */
489
	private static function get_allowed_object_types() {
490
		return array(
491
			'woocommerce_attribute'                       => array(
492
				'table'    => 'woocommerce_attribute_taxonomies',
493
				'id_field' => 'attribute_id',
494
			),
495
			'woocommerce_downloadable_product_permission' => array(
496
				'table'    => 'woocommerce_downloadable_product_permissions',
497
				'id_field' => 'permission_id',
498
			),
499
			'woocommerce_order_item'                      => array(
500
				'table'     => 'woocommerce_order_items',
501
				'id_field'  => 'order_item_id',
502
				'meta_type' => 'order_item',
503
			),
504
			'woocommerce_payment_token'                   => array(
505
				'table'     => 'woocommerce_payment_tokens',
506
				'id_field'  => 'token_id',
507
				'meta_type' => 'payment_token',
508
			),
509
			'woocommerce_tax_rate'                        => array(
510
				'table'          => 'woocommerce_tax_rates',
511
				'id_field'       => 'tax_rate_id',
512
				'child_table'    => 'woocommerce_tax_rate_locations',
513
				'child_id_field' => 'tax_rate_id',
514
			),
515
			'woocommerce_webhook'                         => array(
516
				'table'    => 'wc_webhooks',
517
				'id_field' => 'webhook_id',
518
			),
519
		);
520
	}
521
522
	/**
523
	 * Fetch option row by option name.
524
	 *
525
	 * @access private
526
	 * @static
527
	 *
528
	 * @param string $name The option name.
529
	 * @return object|null Database query result as object format specified or null on failure.
530
	 */
531
	private static function get_option_row( $name ) {
532
		global $wpdb;
533
		return $wpdb->get_row( $wpdb->prepare( "select * from `{$wpdb->options}` where option_name = %s", $name ) );
534
	}
535
}
536