summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/jetpack_vendor/automattic/jetpack-backup/src/class-rest-controller.php')
-rw-r--r--plugins/jetpack/jetpack_vendor/automattic/jetpack-backup/src/class-rest-controller.php526
1 files changed, 526 insertions, 0 deletions
diff --git a/plugins/jetpack/jetpack_vendor/automattic/jetpack-backup/src/class-rest-controller.php b/plugins/jetpack/jetpack_vendor/automattic/jetpack-backup/src/class-rest-controller.php
new file mode 100644
index 00000000..087aba84
--- /dev/null
+++ b/plugins/jetpack/jetpack_vendor/automattic/jetpack-backup/src/class-rest-controller.php
@@ -0,0 +1,526 @@
+<?php
+/**
+ * The Backup Rest Controller class.
+ * Registers the REST routes for Backup.
+ *
+ * @package automattic/jetpack-backup
+ */
+
+namespace Automattic\Jetpack\Backup;
+
+use Automattic\Jetpack\Connection\Rest_Authentication;
+use Automattic\Jetpack\Sync\Actions as Sync_Actions;
+use WP_Error;
+use WP_REST_Request;
+use WP_REST_Server;
+
+/**
+ * Registers the REST routes for Backup.
+ */
+class REST_Controller {
+ /**
+ * Registers the REST routes for Backup.
+ *
+ * @access public
+ * @static
+ */
+ public static function register_rest_routes() {
+ // Install a Helper Script to assist Jetpack Backup fetch data.
+ register_rest_route(
+ 'jetpack/v4',
+ '/backup-helper-script',
+ array(
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => __CLASS__ . '::install_backup_helper_script',
+ 'permission_callback' => __CLASS__ . '::backup_permissions_callback',
+ 'args' => array(
+ 'helper' => array(
+ 'description' => __( 'base64 encoded Backup Helper Script body.', 'jetpack-backup-pkg' ),
+ 'type' => 'string',
+ 'required' => true,
+ ),
+ ),
+ )
+ );
+
+ // Delete a Backup Helper Script.
+ register_rest_route(
+ 'jetpack/v4',
+ '/backup-helper-script',
+ array(
+ 'methods' => WP_REST_Server::DELETABLE,
+ 'callback' => __CLASS__ . '::delete_backup_helper_script',
+ 'permission_callback' => __CLASS__ . '::backup_permissions_callback',
+ 'args' => array(
+ 'path' => array(
+ 'description' => __( 'Path to Backup Helper Script', 'jetpack-backup-pkg' ),
+ 'type' => 'string',
+ 'required' => true,
+ ),
+ ),
+ )
+ );
+
+ // Fetch a backup of a database object, along with all of its metadata.
+ register_rest_route(
+ 'jetpack/v4',
+ '/database-object/backup',
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::fetch_database_object_backup',
+ 'permission_callback' => __CLASS__ . '::backup_permissions_callback',
+ 'args' => array(
+ 'object_type' => array(
+ 'description' => __( 'Type of object to fetch from the database', 'jetpack-backup-pkg' ),
+ 'required' => true,
+ 'validate_callback' => function ( $value ) {
+ if ( ! is_string( $value ) ) {
+ return new WP_Error(
+ 'rest_invalid_param',
+ __( 'The object_type argument must be a non-empty string.', 'jetpack-backup-pkg' ),
+ array( 'status' => 400 )
+ );
+ }
+
+ $allowed_object_types = array_keys( self::get_allowed_object_types() );
+
+ if ( ! in_array( $value, $allowed_object_types, true ) ) {
+ return new WP_Error(
+ 'rest_invalid_param',
+ sprintf(
+ /* translators: %s: comma-separated list of allowed object types */
+ __( 'The object_type argument should be one of %s', 'jetpack-backup-pkg' ),
+ implode( ', ', $allowed_object_types )
+ ),
+ array( 'status' => 400 )
+ );
+ }
+
+ return true;
+ },
+ ),
+ 'object_id' => array(
+ 'description' => __( 'ID of the database object to fetch', 'jetpack-backup-pkg' ),
+ 'type' => 'integer',
+ 'required' => true,
+ ),
+ ),
+ )
+ );
+
+ // Fetch a backup of an option.
+ register_rest_route(
+ 'jetpack/v4',
+ '/options/backup',
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::fetch_options_backup',
+ 'permission_callback' => __CLASS__ . '::backup_permissions_callback',
+ 'args' => array(
+ 'name' => array(
+ 'description' => __( 'One or more option names to include in the backup', 'jetpack-backup-pkg' ),
+ 'validate_callback' => function ( $value ) {
+ $is_valid = is_array( $value ) || is_string( $value );
+ if ( ! $is_valid ) {
+ 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 ) );
+ }
+
+ return true;
+ },
+ 'required' => true,
+ ),
+ ),
+ )
+ );
+
+ // Fetch a backup of a comment, along with all of its metadata.
+ register_rest_route(
+ 'jetpack/v4',
+ '/comments/(?P<id>\d+)/backup',
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::fetch_comment_backup',
+ 'permission_callback' => __CLASS__ . '::backup_permissions_callback',
+ )
+ );
+
+ // Fetch a backup of a post, along with all of its metadata.
+ register_rest_route(
+ 'jetpack/v4',
+ '/posts/(?P<id>\d+)/backup',
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::fetch_post_backup',
+ 'permission_callback' => __CLASS__ . '::backup_permissions_callback',
+ )
+ );
+
+ // Fetch a backup of a term, along with all of its metadata.
+ register_rest_route(
+ 'jetpack/v4',
+ '/terms/(?P<id>\d+)/backup',
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::fetch_term_backup',
+ 'permission_callback' => __CLASS__ . '::backup_permissions_callback',
+ )
+ );
+
+ // Fetch a backup of a user, along with all of its metadata.
+ register_rest_route(
+ 'jetpack/v4',
+ '/users/(?P<id>\d+)/backup',
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::fetch_user_backup',
+ 'permission_callback' => __CLASS__ . '::backup_permissions_callback',
+ )
+ );
+ }
+
+ /**
+ * The Backup endpoints should only be available via site-level authentication.
+ * This means that the corresponding endpoints can only be accessible from WPCOM.
+ *
+ * @access public
+ * @static
+ *
+ * @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
+ */
+ public static function backup_permissions_callback() {
+ if ( Rest_Authentication::is_signed_with_blog_token() ) {
+ return true;
+ }
+
+ $error_msg = esc_html__(
+ 'You are not allowed to perform this action.',
+ 'jetpack-backup-pkg'
+ );
+
+ return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) );
+ }
+
+ /**
+ * Install the Backup Helper Script.
+ *
+ * @access public
+ * @static
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ * @return array|WP_Error Returns the result of Helper Script installation. Returns one of:
+ * - WP_Error on failure, or
+ * - An array with installation info on success:
+ * 'path' (string) The sinstallation path.
+ * 'url' (string) The access url.
+ * 'abspath' (string) The abspath.
+ */
+ public static function install_backup_helper_script( $request ) {
+ $helper_script = $request->get_param( 'helper' );
+
+ // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
+ $helper_script = base64_decode( $helper_script );
+ if ( ! $helper_script ) {
+ return new WP_Error( 'invalid_args', __( 'Helper script body must be base64 encoded', 'jetpack-backup-pkg' ), 400 );
+ }
+
+ $installation_info = Helper_Script_Manager::install_helper_script( $helper_script );
+ Helper_Script_Manager::cleanup_expired_helper_scripts();
+
+ // Include ABSPATH with successful result.
+ if ( ! is_wp_error( $installation_info ) ) {
+ $installation_info['abspath'] = ABSPATH;
+ }
+
+ return rest_ensure_response( $installation_info );
+ }
+
+ /**
+ * Delete a Backup Helper Script.
+ *
+ * @access public
+ * @static
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ * @return array An array with 'success' key indicating the result of the delete operation.
+ */
+ public static function delete_backup_helper_script( $request ) {
+ $path_to_helper_script = $request->get_param( 'path' );
+
+ $deleted = Helper_Script_Manager::delete_helper_script( $path_to_helper_script );
+ Helper_Script_Manager::cleanup_expired_helper_scripts();
+
+ return rest_ensure_response(
+ array(
+ 'success' => $deleted,
+ )
+ );
+ }
+
+ /**
+ * Fetch a backup of a database object, along with all of its metadata.
+ *
+ * @access public
+ * @static
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ * @return array
+ */
+ public static function fetch_database_object_backup( $request ) {
+ global $wpdb;
+
+ // Disable Sync as this is a read-only operation and triggered by sync activity.
+ Sync_Actions::mark_sync_read_only();
+
+ $allowed_object_types = self::get_allowed_object_types();
+ // Safe to do this as we have already validated the object_type key exists in self::get_allowed_object_types().
+ $object_type = $allowed_object_types[ $request->get_param( 'object_type' ) ];
+ $object_id = $request->get_param( 'object_id' );
+ $table = $wpdb->prefix . $object_type['table'];
+ $id_field = $object_type['id_field'];
+
+ // Fetch the requested object.
+ $object = $wpdb->get_row(
+ $wpdb->prepare(
+ 'SELECT * FROM `' . $table . '` WHERE `' . $id_field . '` = %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+ $object_id
+ )
+ );
+
+ if ( empty( $object ) ) {
+ return new WP_Error( 'object_not_found', __( 'Object not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
+ }
+
+ $result = array( 'object' => $object );
+
+ // Fetch associated metadata (if this object type has any).
+ if ( ! empty( $object_type['meta_type'] ) ) {
+ $result['meta'] = get_metadata( $object_type['meta_type'], $object_id );
+ }
+
+ // If there is a child linked table (eg: woocommerce_tax_rate_locations), fetch linked records.
+ if ( ! empty( $object_type['child_table'] ) ) {
+ $child_table = $wpdb->prefix . $object_type['child_table'];
+ $child_id_field = $object_type['child_id_field'];
+
+ $result['children'] = $wpdb->get_results(
+ $wpdb->prepare(
+ 'SELECT * FROM `' . $child_table . '` where `' . $child_id_field . '` = %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+ $object_id
+ )
+ );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Fetch a backup of an option.
+ *
+ * @access public
+ * @static
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ * @return array
+ */
+ public static function fetch_options_backup( $request ) {
+ // Disable Sync as this is a read-only operation and triggered by sync activity.
+ Sync_Actions::mark_sync_read_only();
+
+ $option_names = (array) $request->get_param( 'name' );
+
+ $options = array_map( 'self::get_option_row', $option_names );
+ return array( 'options' => $options );
+ }
+
+ /**
+ * Fetch a backup of a comment, along with all of its metadata.
+ *
+ * @access public
+ * @static
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ * @return array
+ */
+ public static function fetch_comment_backup( $request ) {
+ // Disable Sync as this is a read-only operation and triggered by sync activity.
+ Sync_Actions::mark_sync_read_only();
+
+ $comment_id = $request['id'];
+ $comment = get_comment( $comment_id );
+
+ if ( empty( $comment ) ) {
+ return new WP_Error( 'comment_not_found', __( 'Comment not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
+ }
+
+ $allowed_keys = array(
+ 'comment_ID',
+ 'comment_post_ID',
+ 'comment_author',
+ 'comment_author_email',
+ 'comment_author_url',
+ 'comment_author_IP',
+ 'comment_date',
+ 'comment_date_gmt',
+ 'comment_content',
+ 'comment_karma',
+ 'comment_approved',
+ 'comment_agent',
+ 'comment_type',
+ 'comment_parent',
+ 'user_id',
+ );
+
+ $comment = array_intersect_key( $comment->to_array(), array_flip( $allowed_keys ) );
+
+ $comment_meta = get_comment_meta( $comment['comment_ID'] );
+
+ return array(
+ 'comment' => $comment,
+ 'meta' => is_array( $comment_meta ) ? $comment_meta : array(),
+ );
+ }
+
+ /**
+ * Fetch a backup of a post, along with all of its metadata.
+ *
+ * @access public
+ * @static
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ * @return array
+ */
+ public static function fetch_post_backup( $request ) {
+ global $wpdb;
+
+ // Disable Sync as this is a read-only operation and triggered by sync activity.
+ Sync_Actions::mark_sync_read_only();
+
+ $post_id = $request['id'];
+ $post = get_post( $post_id );
+
+ if ( empty( $post ) ) {
+ return new WP_Error( 'post_not_found', __( 'Post not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
+ }
+
+ // Fetch terms associated with this post object.
+ $terms = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT term_taxonomy_id, term_order FROM {$wpdb->term_relationships} WHERE object_id = %d;",
+ $post->ID
+ )
+ );
+
+ return array(
+ 'post' => (array) $post,
+ 'meta' => get_post_meta( $post->ID ),
+ 'terms' => (array) $terms,
+ );
+ }
+
+ /**
+ * Fetch a backup of a term, along with all of its metadata.
+ *
+ * @access public
+ * @static
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ * @return array
+ */
+ public static function fetch_term_backup( $request ) {
+ // Disable Sync as this is a read-only operation and triggered by sync activity.
+ Sync_Actions::mark_sync_read_only();
+
+ $term_id = $request['id'];
+ $term = get_term( $term_id );
+
+ if ( empty( $term ) ) {
+ return new WP_Error( 'term_not_found', __( 'Term not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
+ }
+
+ return array(
+ 'term' => (array) $term,
+ 'meta' => get_term_meta( $term_id ),
+ );
+ }
+
+ /**
+ * Fetch a backup of a user, along with all of its metadata.
+ *
+ * @access public
+ * @static
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ * @return array
+ */
+ public static function fetch_user_backup( $request ) {
+ // Disable Sync as this is a read-only operation and triggered by sync activity.
+ Sync_Actions::mark_sync_read_only();
+
+ $user_id = $request['id'];
+ $user = get_user_by( 'id', $user_id );
+
+ if ( empty( $user ) ) {
+ return new WP_Error( 'user_not_found', __( 'User not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
+ }
+
+ return array(
+ 'user' => $user->to_array(),
+ 'meta' => get_user_meta( $user->ID ),
+ );
+ }
+
+ /**
+ * Get allowed object types for the '/database-object/backup' endpoint.
+ *
+ * @access private
+ * @static
+ *
+ * @return array
+ */
+ private static function get_allowed_object_types() {
+ return array(
+ 'woocommerce_attribute' => array(
+ 'table' => 'woocommerce_attribute_taxonomies',
+ 'id_field' => 'attribute_id',
+ ),
+ 'woocommerce_downloadable_product_permission' => array(
+ 'table' => 'woocommerce_downloadable_product_permissions',
+ 'id_field' => 'permission_id',
+ ),
+ 'woocommerce_order_item' => array(
+ 'table' => 'woocommerce_order_items',
+ 'id_field' => 'order_item_id',
+ 'meta_type' => 'order_item',
+ ),
+ 'woocommerce_payment_token' => array(
+ 'table' => 'woocommerce_payment_tokens',
+ 'id_field' => 'token_id',
+ 'meta_type' => 'payment_token',
+ ),
+ 'woocommerce_tax_rate' => array(
+ 'table' => 'woocommerce_tax_rates',
+ 'id_field' => 'tax_rate_id',
+ 'child_table' => 'woocommerce_tax_rate_locations',
+ 'child_id_field' => 'tax_rate_id',
+ ),
+ 'woocommerce_webhook' => array(
+ 'table' => 'wc_webhooks',
+ 'id_field' => 'webhook_id',
+ ),
+ );
+ }
+
+ /**
+ * Fetch option row by option name.
+ *
+ * @access private
+ * @static
+ *
+ * @param string $name The option name.
+ * @return object|null Database query result as object format specified or null on failure.
+ */
+ private static function get_option_row( $name ) {
+ global $wpdb;
+ return $wpdb->get_row( $wpdb->prepare( "select * from `{$wpdb->options}` where option_name = %s", $name ) );
+ }
+}