summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php')
-rw-r--r--plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php431
1 files changed, 428 insertions, 3 deletions
diff --git a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php b/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
index 70f8ed62..6af6a1ef 100644
--- a/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
+++ b/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
@@ -17,6 +17,9 @@ require_once ABSPATH . '/wp-includes/class-wp-error.php';
// Register endpoints when WP REST API is initialized.
add_action( 'rest_api_init', array( 'Jetpack_Core_Json_Api_Endpoints', 'register_endpoints' ) );
+// Load API endpoints that are synced with WP.com
+// Each of these is a class that will register its own routes on 'rest_api_init'.
+require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/load-wpcom-endpoints.php';
/**
* Class Jetpack_Core_Json_Api_Endpoints
@@ -102,6 +105,13 @@ class Jetpack_Core_Json_Api_Endpoints {
'callback' => __CLASS__ . '::jetpack_connection_status',
) );
+ // Test current connection status of Jetpack
+ register_rest_route( 'jetpack/v4', '/connection/test', array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::jetpack_connection_test',
+ 'permission_callback' => __CLASS__ . '::manage_modules_permission_check',
+ ) );
+
register_rest_route( 'jetpack/v4', '/rewind', array(
'methods' => WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::get_rewind_data',
@@ -174,6 +184,13 @@ class Jetpack_Core_Json_Api_Endpoints {
'permission_callback' => array( $site_endpoint , 'can_request' ),
) );
+ // Get related posts of a certain site post
+ register_rest_route( 'jetpack/v4', '/site/posts/related', array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $site_endpoint, 'get_related_posts' ),
+ 'permission_callback' => array( $site_endpoint , 'can_request' ),
+ ) );
+
// Confirm that a site in identity crisis should be in staging mode
register_rest_route( 'jetpack/v4', '/identity-crisis/confirm-safe-mode', array(
'methods' => WP_REST_Server::EDITABLE,
@@ -378,6 +395,63 @@ class Jetpack_Core_Json_Api_Endpoints {
'callback' => array( $widget_endpoint, 'process' ),
'permission_callback' => array( $widget_endpoint, 'can_request' ),
) );
+
+ // Site Verify: check if the site is verified, and a get verification token if not
+ register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::is_site_verified_and_token',
+ 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
+ ) );
+
+ register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)/(?<keyring_id>[0-9]+)', array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::is_site_verified_and_token',
+ 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
+ ) );
+
+ // Site Verify: tell a service to verify the site
+ register_rest_route( 'jetpack/v4', '/verify-site/(?P<service>[a-z\-_]+)', array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => __CLASS__ . '::verify_site',
+ 'permission_callback' => __CLASS__ . '::update_settings_permission_check',
+ 'args' => array(
+ 'keyring_id' => array(
+ 'required' => true,
+ 'type' => 'integer',
+ 'validate_callback' => __CLASS__ . '::validate_posint',
+ ),
+ )
+ ) );
+
+ // Get and set API keys.
+ // Note: permission_callback intentionally omitted from the GET method.
+ // Map block requires open access to API keys on the front end.
+ register_rest_route(
+ 'jetpack/v4',
+ '/service-api-keys/(?P<service>[a-z\-_]+)',
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => __CLASS__ . '::get_service_api_key',
+ ),
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => __CLASS__ . '::update_service_api_key',
+ 'permission_callback' => __CLASS__ . '::edit_others_posts_check',
+ 'args' => array(
+ 'service_api_key' => array(
+ 'required' => true,
+ 'type' => 'text',
+ ),
+ ),
+ ),
+ array(
+ 'methods' => WP_REST_Server::DELETABLE,
+ 'callback' => __CLASS__ . '::delete_service_api_key',
+ 'permission_callback' => __CLASS__ . '::edit_others_posts_check',
+ ),
+ )
+ );
}
public static function get_plans( $request ) {
@@ -461,6 +535,112 @@ class Jetpack_Core_Json_Api_Endpoints {
return $result;
}
+
+ /**
+ * Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
+ * keyring to use to get the token if it is not
+ *
+ * Returns 'verified' = true/false, and a token if 'verified' is false and site is ready for verification
+ *
+ * @since 6.6.0
+ *
+ * @param WP_REST_Request $request The request sent to the WP REST API.
+ *
+ * @return array|wp-error
+ */
+ public static function is_site_verified_and_token( $request ) {
+ /**
+ * Return an error if the site uses a Maintenance / Coming Soon plugin
+ * and if the plugin is configured to make the site private.
+ *
+ * We currently handle the following plugins:
+ * - https://github.com/mojoness/mojo-marketplace-wp-plugin (used by bluehost)
+ * - https://wordpress.org/plugins/mojo-under-construction
+ * - https://wordpress.org/plugins/under-construction-page
+ * - https://wordpress.org/plugins/ultimate-under-construction
+ * - https://wordpress.org/plugins/coming-soon
+ *
+ * You can handle this in your own plugin thanks to the `jetpack_is_under_construction_plugin` filter.
+ * If the filter returns true, we will consider the site as under construction.
+ */
+ $mm_coming_soon = get_option( 'mm_coming_soon', null );
+ $under_construction_activation_status = get_option( 'underConstructionActivationStatus', null );
+ $ucp_options = get_option( 'ucp_options', array() );
+ $uuc_settings = get_option( 'uuc_settings', array() );
+ $csp4 = get_option( 'seed_csp4_settings_content', array() );
+ if (
+ ( Jetpack::is_plugin_active( 'mojo-marketplace-wp-plugin/mojo-marketplace.php' ) && 'true' === $mm_coming_soon )
+ || Jetpack::is_plugin_active( 'mojo-under-construction/mojo-contruction.php' ) && 1 == $under_construction_activation_status // WPCS: loose comparison ok.
+ || ( Jetpack::is_plugin_active( 'under-construction-page/under-construction.php' ) && isset( $ucp_options['status'] ) && 1 == $ucp_options['status'] ) // WPCS: loose comparison ok.
+ || ( Jetpack::is_plugin_active( 'ultimate-under-construction/ultimate-under-construction.php' ) && isset( $uuc_settings['enable'] ) && 1 == $uuc_settings['enable'] ) // WPCS: loose comparison ok.
+ || ( Jetpack::is_plugin_active( 'coming-soon/coming-soon.php' ) && isset( $csp4['status'] ) && ( 1 == $csp4['status'] || 2 == $csp4['status'] ) ) // WPCS: loose comparison ok.
+ /**
+ * Allow plugins to mark a site as "under construction".
+ *
+ * @since 6.7.0
+ *
+ * @param false bool Is the site under construction? Default to false.
+ */
+ || true === apply_filters( 'jetpack_is_under_construction_plugin', false )
+ ) {
+ return new WP_Error( 'forbidden', __( 'Site is under construction and cannot be verified', 'jetpack' ) );
+ }
+
+ Jetpack::load_xml_rpc_client();
+ $xml = new Jetpack_IXR_Client( array(
+ 'user_id' => get_current_user_id(),
+ ) );
+
+ $args = array(
+ 'user_id' => get_current_user_id(),
+ 'service' => $request[ 'service' ],
+ );
+
+ if ( isset( $request[ 'keyring_id' ] ) ) {
+ $args[ 'keyring_id' ] = $request[ 'keyring_id' ];
+ }
+
+ $xml->query( 'jetpack.isSiteVerified', $args );
+
+ if ( $xml->isError() ) {
+ return new WP_Error( 'error_checking_if_site_verified_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
+ } else {
+ return $xml->getResponse();
+ }
+ }
+
+
+
+ public static function verify_site( $request ) {
+ Jetpack::load_xml_rpc_client();
+ $xml = new Jetpack_IXR_Client( array(
+ 'user_id' => get_current_user_id(),
+ ) );
+
+ $params = $request->get_json_params();
+
+ $xml->query( 'jetpack.verifySite', array(
+ 'user_id' => get_current_user_id(),
+ 'service' => $request[ 'service' ],
+ 'keyring_id' => $params[ 'keyring_id' ],
+ )
+ );
+
+ if ( $xml->isError() ) {
+ return new WP_Error( 'error_verifying_site_google', sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
+ } else {
+ $response = $xml->getResponse();
+
+ if ( ! empty( $response['errors'] ) ) {
+ $error = new WP_Error;
+ $error->errors = $response['errors'];
+ return $error;
+ }
+
+ return $response;
+ }
+ }
+
/**
* Handles verification that a site is registered
*
@@ -688,6 +868,19 @@ class Jetpack_Core_Json_Api_Endpoints {
}
/**
+ * Verify that user can edit other's posts (Editors and Administrators).
+ *
+ * @return bool Whether user has the capability 'edit_others_posts'.
+ */
+ public static function edit_others_posts_check() {
+ if ( current_user_can( 'edit_others_posts' ) ) {
+ return true;
+ }
+
+ return new WP_Error( 'invalid_user_permission_edit_others_posts', self::$user_permissions_error_msg, array( 'status' => self::rest_authorization_required_code() ) );
+ }
+
+ /**
* Contextual HTTP error code for authorization failure.
*
* Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
@@ -722,6 +915,43 @@ class Jetpack_Core_Json_Api_Endpoints {
);
}
+ /**
+ * Test connection status for this Jetpack site. It uses the /jetpack-blogs/%d/test-connection wpcom endpoint.
+ *
+ * @since 6.8.0
+ *
+ * @return array|WP_Error WP_Error returned if connection test does not succeed.
+ */
+ public static function jetpack_connection_test() {
+ $response = Jetpack_Client::wpcom_json_api_request_as_blog(
+ sprintf( '/jetpack-blogs/%d/test-connection', Jetpack_Options::get_option( 'id' ) ),
+ Jetpack_Client::WPCOM_JSON_API_VERSION
+ );
+
+ if ( is_wp_error( $response ) ) {
+ /* translators: %1$s is the error code, %2$s is the error message */
+ return new WP_Error( 'connection_test_failed', sprintf( __( 'Connection test failed (#%1$s: %2$s)', 'jetpack' ), $response->get_error_code(), $response->get_error_message() ), array( 'status' => $response->get_error_code() ) );
+ }
+
+ $body = wp_remote_retrieve_body( $response );
+ if ( ! $body ) {
+ return new WP_Error( 'connection_test_failed', __( 'Connection test failed (empty response body)', 'jetpack' ), array( 'status' => $response->get_error_code() ) );
+ }
+
+ $result = json_decode( $body );
+ $is_connected = (bool) $result->connected;
+ $message = $result->message;
+
+ if ( $is_connected ) {
+ return rest_ensure_response( array(
+ 'code' => 'success',
+ 'message' => $message,
+ ) );
+ } else {
+ return new WP_Error( 'connection_test_failed', $message, array( 'status' => $response->get_error_code() ) );
+ }
+ }
+
public static function rewind_data() {
$site_id = Jetpack_Options::get_option( 'id' );
@@ -881,11 +1111,22 @@ class Jetpack_Core_Json_Api_Endpoints {
);
}
+ // Update the master user in Jetpack
$updated = Jetpack_Options::update_option( 'master_user', $new_owner_id );
- if ( $updated ) {
+
+ // Notify WPCOM about the master user change
+ Jetpack::load_xml_rpc_client();
+ $xml = new Jetpack_IXR_Client( array(
+ 'user_id' => get_current_user_id(),
+ ) );
+ $xml->query( 'jetpack.switchBlogOwner', array(
+ 'new_blog_owner' => $new_owner_id,
+ ) );
+
+ if ( $updated && ! $xml->isError() ) {
return rest_ensure_response(
array(
- 'code' => 'success'
+ 'code' => 'success',
)
);
}
@@ -1916,6 +2157,14 @@ class Jetpack_Core_Json_Api_Endpoints {
'validate_callback' => __CLASS__ . '::validate_boolean',
'jp_group' => 'wordads',
),
+ 'wordads_custom_adstxt' => array(
+ 'description' => esc_html__( 'Custom ads.txt entries', 'jetpack' ),
+ 'type' => 'string',
+ 'default' => '',
+ 'validate_callback' => __CLASS__ . '::validate_string',
+ 'sanitize_callback' => 'sanitize_textarea_field',
+ 'jp_group' => 'wordads',
+ ),
// Google Analytics
'google_analytics_tracking_id' => array(
@@ -2253,7 +2502,7 @@ class Jetpack_Core_Json_Api_Endpoints {
* @return bool|WP_Error
*/
public static function validate_verification_service( $value = '', $request, $param ) {
- if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || preg_match( '#^<meta name="([a-z0-9_\-.:]+)?" content="([a-z0-9_-]+)?" />$#i', $value ) ) ) ) {
+ if ( ! empty( $value ) && ! ( is_string( $value ) && ( preg_match( '/^[a-z0-9_-]+$/i', $value ) || jetpack_verification_get_code( $value ) !== false ) ) ) {
return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an alphanumeric string or a verification tag.', 'jetpack' ), $param ) );
}
return true;
@@ -2831,6 +3080,182 @@ class Jetpack_Core_Json_Api_Endpoints {
return array();
}
+
+ /**
+ * Get third party plugin API keys.
+ *
+ * @param WP_REST_Request $request {
+ * Array of parameters received by request.
+ *
+ * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
+ * }
+ */
+ public static function get_service_api_key( $request ) {
+ $service = self::validate_service_api_service( $request['service'] );
+ if ( ! $service ) {
+ return self::service_api_invalid_service_response();
+ }
+ $option = self::key_for_api_service( $service );
+ $message = esc_html__( 'API key retrieved successfully.', 'jetpack' );
+ return array(
+ 'code' => 'success',
+ 'service' => $service,
+ 'service_api_key' => Jetpack_Options::get_option( $option, '' ),
+ 'message' => $message,
+ );
+ }
+
+ /**
+ * Update third party plugin API keys.
+ *
+ * @param WP_REST_Request $request {
+ * Array of parameters received by request.
+ *
+ * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
+ * }
+ */
+ public static function update_service_api_key( $request ) {
+ $service = self::validate_service_api_service( $request['service'] );
+ if ( ! $service ) {
+ return self::service_api_invalid_service_response();
+ }
+ $params = $request->get_json_params();
+ $service_api_key = trim( $params['service_api_key'] );
+ $option = self::key_for_api_service( $service );
+ $validation = self::validate_service_api_key( $service_api_key, $service );
+ if ( ! $validation['status'] ) {
+ return new WP_Error( 'invalid_key', esc_html__( 'Invalid API Key', 'jetpack' ), array( 'status' => 404 ) );
+ }
+ $message = esc_html__( 'API key updated successfully.', 'jetpack' );
+ Jetpack_Options::update_option( $option, $service_api_key );
+ return array(
+ 'code' => 'success',
+ 'service' => $service,
+ 'service_api_key' => Jetpack_Options::get_option( $option, '' ),
+ 'message' => $message,
+ );
+ }
+
+ /**
+ * Delete a third party plugin API key.
+ *
+ * @param WP_REST_Request $request {
+ * Array of parameters received by request.
+ *
+ * @type string $slug Plugin slug with the syntax 'plugin-directory/plugin-main-file.php'.
+ * }
+ */
+ public static function delete_service_api_key( $request ) {
+ $service = self::validate_service_api_service( $request['service'] );
+ if ( ! $service ) {
+ return self::service_api_invalid_service_response();
+ }
+ $option = self::key_for_api_service( $service );
+ Jetpack_Options::delete_option( $option );
+ $message = esc_html__( 'API key deleted successfully.', 'jetpack' );
+ return array(
+ 'code' => 'success',
+ 'service' => $service,
+ 'service_api_key' => Jetpack_Options::get_option( $option, '' ),
+ 'message' => $message,
+ );
+ }
+
+ /**
+ * Validate the service provided in /service-api-keys/ endpoints.
+ * To add a service to these endpoints, add the service name to $valid_services
+ * and add '{service name}_api_key' to the non-compact return array in get_option_names(),
+ * in class-jetpack-options.php
+ *
+ * @param string $service The service the API key is for.
+ * @return string Returns the service name if valid, null if invalid.
+ */
+ public static function validate_service_api_service( $service = null ) {
+ $valid_services = array(
+ 'mapbox',
+ );
+ return in_array( $service, $valid_services, true ) ? $service : null;
+ }
+
+ /**
+ * Error response for invalid service API key requests with an invalid service.
+ */
+ public static function service_api_invalid_service_response() {
+ return new WP_Error(
+ 'invalid_service',
+ esc_html__( 'Invalid Service', 'jetpack' ),
+ array( 'status' => 404 )
+ );
+ }
+
+ /**
+ * Validate API Key
+ *
+ * @param string $key The API key to be validated.
+ * @param string $service The service the API key is for.
+ */
+ public static function validate_service_api_key( $key = null, $service = null ) {
+ $validation = false;
+ switch ( $service ) {
+ case 'mapbox':
+ $validation = self::validate_service_api_key_mapbox( $key );
+ break;
+ }
+ return $validation;
+ }
+
+ /**
+ * Validate Mapbox API key
+ * Based loosely on https://github.com/mapbox/geocoding-example/blob/master/php/MapboxTest.php
+ *
+ * @param string $key The API key to be validated.
+ */
+ public static function validate_service_api_key_mapbox( $key ) {
+ $status = true;
+ $msg = null;
+ $mapbox_url = sprintf(
+ 'https://api.mapbox.com?%s',
+ $key
+ );
+ $mapbox_response = wp_safe_remote_get( esc_url_raw( $mapbox_url ) );
+ $mapbox_body = wp_remote_retrieve_body( $mapbox_response );
+ if ( '{"api":"mapbox"}' !== $mapbox_body ) {
+ $status = false;
+ $msg = esc_html__( 'Can\'t connect to Mapbox', 'jetpack' );
+ return array(
+ 'status' => $status,
+ 'error_message' => $msg,
+ );
+ }
+ $mapbox_geocode_url = esc_url_raw(
+ sprintf(
+ 'https://api.mapbox.com/geocoding/v5/mapbox.places/%s.json?access_token=%s',
+ '1+broadway+new+york+ny+usa',
+ $key
+ )
+ );
+ $mapbox_geocode_response = wp_safe_remote_get( esc_url_raw( $mapbox_geocode_url ) );
+ $mapbox_geocode_body = wp_remote_retrieve_body( $mapbox_geocode_response );
+ $mapbox_geocode_json = json_decode( $mapbox_geocode_body );
+ if ( isset( $mapbox_geocode_json->message ) && ! isset( $mapbox_geocode_json->query ) ) {
+ $status = false;
+ $msg = $mapbox_geocode_json->message;
+ }
+ return array(
+ 'status' => $status,
+ 'error_message' => $msg,
+ );
+ }
+
+ /**
+ * Create site option key for service
+ *
+ * @param string $service The service to create key for.
+ */
+ private static function key_for_api_service( $service ) {
+ return $service . '_api_key';
+ }
+
/**
* Checks if the queried plugin is active.
*