Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetpack core api: Refactor and reuse trait for proxying requests to WPCOM #40245

Merged
merged 16 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions projects/plugins/jetpack/.phan/baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
// PhanRedundantCondition : 70+ occurrences
// PhanDeprecatedFunction : 65+ occurrences
// PhanPossiblyUndeclaredVariable : 60+ occurrences
// PhanTypeArraySuspiciousNullable : 60+ occurrences
// PhanRedefineFunction : 55+ occurrences
// PhanTypeArraySuspiciousNullable : 50+ occurrences
// PhanTypeMismatchArgumentNullable : 50+ occurrences
// PhanParamTooMany : 40+ occurrences
// PhanPluginDuplicateAdjacentStatement : 40+ occurrences
// PhanTypeExpectedObjectPropAccess : 40+ occurrences
// PhanTypeMismatchArgumentInternal : 40+ occurrences
// PhanUndeclaredProperty : 35+ occurrences
// PhanUndeclaredProperty : 40+ occurrences
// PhanParamSignatureMismatch : 25+ occurrences
// PhanTypeMismatchDefault : 25+ occurrences
// PhanTypeMismatchPropertyProbablyReal : 25+ occurrences
Expand Down Expand Up @@ -58,12 +58,12 @@
// PhanDeprecatedClass : 5 occurrences
// PhanNonClassMethodCall : 5 occurrences
// PhanTypeMismatchDimAssignment : 5 occurrences
// PhanTypeSuspiciousStringExpression : 5 occurrences
// PhanAccessMethodInternal : 4 occurrences
// PhanImpossibleCondition : 4 occurrences
// PhanTypeInvalidLeftOperandOfAdd : 4 occurrences
// PhanTypeInvalidLeftOperandOfBitwiseOp : 4 occurrences
// PhanTypeInvalidRightOperandOfBitwiseOp : 4 occurrences
// PhanTypeSuspiciousStringExpression : 4 occurrences
// PhanUndeclaredFunctionInCallable : 4 occurrences
// PhanDeprecatedFunctionInternal : 3 occurrences
// PhanDeprecatedTrait : 3 occurrences
Expand Down Expand Up @@ -134,9 +134,9 @@
'_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php' => ['PhanPluginMixedKeyNoKey'],
'_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-send-email-preview.php' => ['PhanTypeMismatchReturn'],
'_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-template-loader.php' => ['PhanTypeMismatchReturnProbablyReal'],
'_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v3-endpoint-blogging-prompts.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanPluginMixedKeyNoKey', 'PhanTypeInvalidLeftOperandOfAdd', 'PhanTypeMismatchArgumentInternal', 'PhanTypeMismatchReturnProbablyReal'],
'_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v3-endpoint-blogging-prompts.php' => ['PhanPluginMixedKeyNoKey', 'PhanTypeInvalidLeftOperandOfAdd', 'PhanTypeMismatchArgumentInternal', 'PhanTypeMismatchReturnProbablyReal'],
'_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php' => ['PhanPluginMixedKeyNoKey'],
'_inc/lib/core-api/wpcom-endpoints/memberships.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanPluginUnreachableCode', 'PhanTypeArraySuspicious', 'PhanTypeArraySuspiciousNullable', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal', 'PhanTypeSuspiciousStringExpression'],
'_inc/lib/core-api/wpcom-endpoints/memberships.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanPluginUnreachableCode', 'PhanTypeArraySuspicious'],
'_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php' => ['PhanPluginMixedKeyNoKey', 'PhanTypeMismatchArgument'],
'_inc/lib/core-api/wpcom-endpoints/publicize-connections.php' => ['PhanParamSignatureMismatch', 'PhanPluginMixedKeyNoKey', 'PhanTypeMismatchArgument'],
'_inc/lib/core-api/wpcom-endpoints/publicize-services.php' => ['PhanParamSignatureMismatch', 'PhanPluginMixedKeyNoKey', 'PhanTypeMismatchArgument'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
* @package automattic/jetpack
*/

use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager;
// Ensure WPCOM_REST_API_Proxy_Request_Trait is present.
require_once __DIR__ . '/trait-wpcom-rest-api-proxy-request-trait.php';

/**
* REST API endpoint wpcom/v3/sites/%s/blogging-prompts.
*/
class WPCOM_REST_API_V3_Endpoint_Blogging_Prompts extends WP_REST_Posts_Controller {

use WPCOM_REST_API_Proxy_Request_Trait;

const TEMPLATE_BLOG_ID = 205876834;

/**
Expand Down Expand Up @@ -42,7 +45,9 @@ class WPCOM_REST_API_V3_Endpoint_Blogging_Prompts extends WP_REST_Posts_Controll
*/
public function __construct() {
$this->post_type = 'post';
$this->namespace = 'wpcom/v3';
$this->base_api_path = 'wpcom';
$this->version = 'v3';
$this->namespace = $this->base_api_path . '/' . $this->version;
$this->rest_base = 'blogging-prompts';
$this->wpcom_is_wpcom_only_endpoint = true;
$this->wpcom_is_site_specific_endpoint = true;
Expand Down Expand Up @@ -99,7 +104,7 @@ public function register_routes() {
*/
public function get_items( $request ) {
if ( ! $this->is_wpcom ) {
return $this->proxy_request_to_wpcom( $request );
return $this->proxy_request_to_wpcom( $request, '', 'user', true );
}

if ( $request->get_param( 'force_year' ) ) {
Expand All @@ -125,7 +130,7 @@ public function get_items( $request ) {
*/
public function get_item( $request ) {
if ( ! $this->is_wpcom ) {
return $this->proxy_request_to_wpcom( $request, $request->get_param( 'id' ) );
return $this->proxy_request_to_wpcom( $request, $request->get_param( 'id' ), 'user', true );
}

if ( $request->get_param( 'force_year' ) ) {
Expand Down Expand Up @@ -524,39 +529,6 @@ public function permissions_check() {
);
}

/**
* Proxy request to wpcom servers for the site and user.
*
* @param WP_Rest_Request $request Request to proxy.
* @param string $path Path to append to the rest base.
* @return mixed|WP_Error Response from wpcom servers or an error.
*/
public function proxy_request_to_wpcom( $request, $path = '' ) {
$blog_id = \Jetpack_Options::get_option( 'id' );
$path = '/sites/' . rawurldecode( $blog_id ) . '/' . rawurldecode( $this->rest_base ) . ( $path ? '/' . rawurldecode( $path ) : '' );
$api_url = add_query_arg( $request->get_query_params(), $path );

// Prefer request as user, if possible. Fall back to blog request to show prompt data for unconnected users.
$response = ( new Manager() )->is_user_connected()
? Client::wpcom_json_api_request_as_user( $api_url, '3', array(), null, 'wpcom' )
: Client::wpcom_json_api_request_as_blog( $api_url, 'v3', array(), null, 'wpcom' );

if ( is_wp_error( $response ) ) {
return $response;
}

$response_status = wp_remote_retrieve_response_code( $response );
$response_body = json_decode( wp_remote_retrieve_body( $response ), true );

if ( $response_status >= 400 ) {
$code = isset( $response_body['code'] ) ? $response_body['code'] : 'unknown_error';
$message = isset( $response_body['message'] ) ? $response_body['message'] : __( 'An unknown error occurred.', 'jetpack' );
return new WP_Error( $code, $message, array( 'status' => $response_status ) );
}

return $response_body;
}

/**
* Creates a sample of users who have answered a blogging prompt.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@
* @since 7.3.0
*/

use Automattic\Jetpack\Connection\Client;
require_once __DIR__ . '/trait-wpcom-rest-api-proxy-request-trait.php';

/**
* Class WPCOM_REST_API_V2_Endpoint_Memberships
* This introduces V2 endpoints.
*/
class WPCOM_REST_API_V2_Endpoint_Memberships extends WP_REST_Controller {

use WPCOM_REST_API_Proxy_Request_Trait;

/**
* WPCOM_REST_API_V2_Endpoint_Memberships constructor.
*/
public function __construct() {
$this->namespace = 'wpcom/v2';
$this->base_api_path = 'wpcom';
$this->version = 'v2';
$this->namespace = $this->base_api_path . '/' . $this->version;
$this->rest_base = 'memberships';
$this->wpcom_is_wpcom_only_endpoint = true;
$this->wpcom_is_site_specific_endpoint = true;
Expand Down Expand Up @@ -86,7 +90,7 @@ public function register_routes() {
'required' => true,
),
'price' => array(
'type' => 'float',
'type' => 'number',
'required' => true,
),
'currency' => array(
Expand Down Expand Up @@ -120,6 +124,20 @@ public function register_routes() {
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_products' ),
'permission_callback' => array( $this, 'can_modify_products_permission_check' ),
'args' => array(
'currency' => array(
'type' => 'string',
'required' => true,
),
'type' => array(
'type' => 'string',
'required' => true,
),
'is_editable' => array(
'type' => 'boolean',
'required' => false,
),
),
),
array(
'methods' => WP_REST_Server::READABLE,
Expand All @@ -142,7 +160,7 @@ public function register_routes() {
'required' => true,
),
'price' => array(
'type' => 'float',
'type' => 'number',
'required' => true,
),
'currency' => array(
Expand Down Expand Up @@ -221,37 +239,7 @@ public function create_products( $request ) {
}
return $result;
} else {
$payload = array(
'type' => $request['type'],
'currency' => $request['currency'],
);

// If we pass directly is_editable as null, it would break API argument validation.
if ( null !== $is_editable ) {
$payload['is_editable'] = $is_editable;
}

$blog_id = Jetpack_Options::get_option( 'id' );
$response = Client::wpcom_json_api_request_as_user(
"/sites/$blog_id/{$this->rest_base}/products",
'v2',
array(
'method' => 'POST',
),
$payload
);
if ( is_wp_error( $response ) ) {
if ( $response->get_error_code() === 'missing_token' ) {
return new WP_Error( 'missing_token', __( 'Please connect your user account to WordPress.com', 'jetpack' ), array( 'status' => 404 ) );
}
return new WP_Error( 'wpcom_connection_error', __( 'Could not connect to WordPress.com', 'jetpack' ), array( 'status' => 404 ) );
}
$data = isset( $response['body'] ) ? json_decode( $response['body'], true ) : null;
// If endpoint returned error, we have to detect it.
if ( 200 !== $response['response']['code'] && $data['code'] ) {
return new WP_Error( $data['code'], $data['message'] ? $data['message'] : '', array( 'status' => 401 ) );
}
return $data;
return $this->proxy_request_to_wpcom_as_user( $request, 'products' );
}

return $request;
Expand All @@ -265,7 +253,6 @@ public function create_products( $request ) {
* @return WP_Error|array ['products']
*/
public function list_products( WP_REST_Request $request ) {
$query = null;
$is_editable = isset( $request['is_editable'] ) ? (bool) $request['is_editable'] : null;
$type = isset( $request['type'] ) ? $request['type'] : null;

Expand All @@ -278,17 +265,8 @@ public function list_products( WP_REST_Request $request ) {
return array( 'error' => $e->getMessage() );
}
} else {
$query_parts = array();
if ( $type !== null ) {
$query_parts[] = 'type=' . $type;
}
if ( $is_editable !== null ) {
$query_parts[] = 'is_editable=' . $is_editable;
}
if ( ! empty( $query_parts ) ) {
$query = '?' . implode( '&', $query_parts );
}
return $this->proxy_request_to_wpcom( "products$query", 'GET' );

return $this->proxy_request_to_wpcom_as_user( $request, 'products' );
}
}

Expand All @@ -310,7 +288,7 @@ public function create_product( WP_REST_Request $request ) {
return array( 'error' => $e->getMessage() );
}
} else {
return $this->proxy_request_to_wpcom( 'product', 'POST', $payload );
return $this->proxy_request_to_wpcom_as_user( $request, 'product' );
}
}

Expand All @@ -333,7 +311,7 @@ public function update_product( \WP_REST_Request $request ) {
return array( 'error' => $e->getMessage() );
}
} else {
return $this->proxy_request_to_wpcom( "product/$product_id", 'POST', $payload );
return $this->proxy_request_to_wpcom_as_user( $request, "product/$product_id" );
}
}

Expand All @@ -356,11 +334,7 @@ public function delete_product( \WP_REST_Request $request ) {
return array( 'error' => $e->getMessage() );
}
} else {
return $this->proxy_request_to_wpcom(
"product/$product_id",
'DELETE',
array( 'cancel_subscriptions' => $cancel_subscriptions )
);
return $this->proxy_request_to_wpcom_as_user( $request, "product/$product_id" );
}
}

Expand Down Expand Up @@ -411,79 +385,10 @@ public function get_status( \WP_REST_Request $request ) {

return (array) $membership_settings;
} else {
$payload = array(
'type' => $request['type'],
'source' => $source,
);

// If we pass directly is_editable as null, it would break API argument validation.
// This also needs to be converted to int because boolean false is ignored by add_query_arg.
if ( null !== $is_editable ) {
$payload['is_editable'] = (int) $is_editable;
}

$blog_id = Jetpack_Options::get_option( 'id' );
$path = "/sites/$blog_id/{$this->rest_base}/status";
if ( $product_type ) {
$path = add_query_arg(
$payload,
$path
);
}
$response = Client::wpcom_json_api_request_as_user( $path, 'v2' );
if ( is_wp_error( $response ) ) {
if ( $response->get_error_code() === 'missing_token' ) {
return new WP_Error( 'missing_token', __( 'Please connect your user account to WordPress.com', 'jetpack' ), array( 'status' => 404 ) );
}
return new WP_Error( 'wpcom_connection_error', __( 'Could not connect to WordPress.com', 'jetpack' ), array( 'status' => 404 ) );
}
$data = isset( $response['body'] ) ? json_decode( $response['body'], true ) : null;
if ( 200 !== $response['response']['code'] && $data['code'] && $data['message'] ) {
return new WP_Error( $data['code'], $data['message'], array( 'status' => 401 ) );
}
return $data;
return $this->proxy_request_to_wpcom_as_user( $request, 'status' );
}
}

/**
* Proxy a request to WPCOM, look for errors and return a response or a WP_Error.
*
* @param string $uri Whatever would go at the end of the url after /sites/$blog_id/$this->rest_base/. This is usually `product`, `products`, or `product/$product_id`.
* @param string $method The HTTP method being used.
* @param array|null $payload An optional payload to be sent with the request.
* @return string The response from WPCOM
*/
private function proxy_request_to_wpcom( $uri, $method, $payload = null ) {
// get blog id
$blog_id = Jetpack_Options::get_option( 'id' );

// proxy request to wpcom
$response = Client::wpcom_json_api_request_as_user(
"/sites/$blog_id/{$this->rest_base}/$uri",
'v2',
array(
'method' => strtoupper( $method ),
),
$payload
);
if ( is_wp_error( $response ) ) {
if ( $response->get_error_code() === 'missing_token' ) {
return new WP_Error( 'missing_token', __( 'Please connect your user account to WordPress.com', 'jetpack' ), array( 'status' => 404 ) );
}
return new WP_Error( 'wpcom_connection_error', __( 'Could not connect to WordPress.com', 'jetpack' ), array( 'status' => 404 ) );
}

// decode response
$data = isset( $response['body'] ) ? json_decode( $response['body'], true ) : null;
// If endpoint returned error, we have to detect it.
if ( 200 !== $response['response']['code'] && $data['code'] && $data['message'] ) {
return new WP_Error( $data['code'], $data['message'], array( 'status' => 401 ) );
}

// return response
return $data;
}

/**
* This function throws an exception if it is run outside of wpcom.
*
Expand Down
Loading
Loading