From 3a60d13a79adb8f2babc1e9eaf1cf4633517bcc7 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Wed, 18 Sep 2024 11:39:13 +0200 Subject: [PATCH 01/30] Connection: REST access for jsonAPI endpoints. --- .../changelog/add-json-api-direct-access | 4 + projects/plugins/jetpack/class.jetpack.php | 16 +++ .../jetpack/class.json-api-endpoints.php | 105 ++++++++++++++++-- ...class.wpcom-json-api-get-post-endpoint.php | 1 + 4 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/add-json-api-direct-access diff --git a/projects/plugins/jetpack/changelog/add-json-api-direct-access b/projects/plugins/jetpack/changelog/add-json-api-direct-access new file mode 100644 index 0000000000000..c72e6ceb90d66 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-json-api-direct-access @@ -0,0 +1,4 @@ +Significance: minor +Type: other + +Add REST for jsonAPI endpoints. diff --git a/projects/plugins/jetpack/class.jetpack.php b/projects/plugins/jetpack/class.jetpack.php index f75b4baf918fb..cab11f07f4547 100644 --- a/projects/plugins/jetpack/class.jetpack.php +++ b/projects/plugins/jetpack/class.jetpack.php @@ -933,8 +933,11 @@ function ( $methods ) { if ( $is_connection_ready ) { require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-iframe-embed.php'; add_action( 'init', array( 'Jetpack_Iframe_Embed', 'init' ), 9, 0 ); + require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-keyring-service-helper.php'; add_action( 'init', array( 'Jetpack_Keyring_Service_Helper', 'init' ), 9, 0 ); + + add_action( 'rest_api_init', array( $this, 'maybe_initialize_rest_jsonapi' ) ); } } @@ -6340,6 +6343,19 @@ public function run_initialize_tracking_action() { do_action( 'jetpack_initialize_tracking' ); } + /** + * Initialize REST jsonAPI if needed. + * + * @return void + */ + public function maybe_initialize_rest_jsonapi() { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) { + define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' ); + require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php'; + } + } + /** * Run plugin post-activation actions if we need to. * diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 68e2288596ace..955a64313c063 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -6,6 +6,7 @@ */ use Automattic\Jetpack\Connection\Client; +use Automattic\Jetpack\Connection\Manager; use Automattic\Jetpack\Status; require_once __DIR__ . '/json-api-config.php'; @@ -124,6 +125,13 @@ abstract class WPCOM_JSON_API_Endpoint { */ public $path_labels = array(); + /** + * Use to initialize the REST endpoint accessible directly. + * + * @var bool + */ + public $allow_rest_access = false; + /** * Accepted query parameters * @@ -300,6 +308,7 @@ public function __construct( $args ) { 'new_version' => WPCOM_JSON_API__CURRENT_VERSION, 'jp_disabled' => false, 'path_labels' => array(), + 'allow_rest_access' => false, 'request_format' => array(), 'response_format' => array(), 'query_parameters' => array(), @@ -331,13 +340,14 @@ public function __construct( $args ) { $this->force = $args['force']; $this->jp_disabled = $args['jp_disabled']; - $this->method = $args['method']; - $this->path = $args['path']; - $this->path_labels = $args['path_labels']; - $this->min_version = $args['min_version']; - $this->max_version = $args['max_version']; - $this->deprecated = $args['deprecated']; - $this->new_version = $args['new_version']; + $this->method = $args['method']; + $this->path = $args['path']; + $this->path_labels = $args['path_labels']; + $this->allow_rest_access = $args['allow_rest_access']; + $this->min_version = $args['min_version']; + $this->max_version = $args['max_version']; + $this->deprecated = $args['deprecated']; + $this->new_version = $args['new_version']; // Ensure max version is not less than min version. if ( version_compare( $this->min_version, $this->max_version, '>' ) ) { @@ -387,6 +397,10 @@ public function __construct( $args ) { $this->example_response = $args['example_response']; $this->api->add( $this ); + + if ( $this->allow_rest_access ) { + $this->create_rest_route_for_endpoint(); + } } /** @@ -2618,6 +2632,83 @@ public function get_amp_cache_origins( $siteurl ) { ); } + /** + * Register a REST route for this jsonAPI endpoint. + * + * @return void + * @throws Exception The exception if something goes wrong. + */ + public function create_rest_route_for_endpoint() { + $route = ''; + $path_parsed = explode( '/', $this->path ); + reset( $this->path_labels ); + + for ( $i = 0, $count = count( $path_parsed ); $i < $count; ++$i ) { + $part = $path_parsed[ $i ]; + + if ( ! $part ) { + continue; + } + + if ( 'sites' === $part ) { + // We don't need the site ID part. + ++$i; + next( $this->path_labels ); + continue; + } + if ( false === strpos( $part, '%' ) ) { + $route .= '/' . $part; + continue; + } + + switch ( $part ) { + case '%s': + $regex = '\w+'; + break; + case '%d': + $regex = '\d+'; + break; + default: + throw new Exception( 'We do not know what to do with this kind of parameter ¯\_(ツ)_/¯' ); + } + + $key = substr( key( $this->path_labels ), 1 ); + next( $this->path_labels ); + + $route .= "/(?P<{$key}>{$regex})"; + } + + register_rest_route( + 'wpcom/v1', + $route, + array( + 'methods' => $this->method, + 'callback' => array( $this, 'rest_callback' ), + ) + ); + } + + /** + * Handle the rest call. + * + * @param WP_REST_Request $request The request object. + * + * @return mixed|WP_Error + */ + public function rest_callback( WP_REST_Request $request ) { + $manager = new Manager( 'jetpack' ); + if ( ! $manager->is_connected() ) { + return new WP_Error( 'site_not_connected' ); + } + + $blog_id = \Jetpack_Options::get_option( 'id' ); + + return call_user_func_array( + array( $this, 'callback' ), + array_values( array( $this->path, $blog_id ) + $request->get_url_params() ) + ); + } + /** * Return endpoint response * diff --git a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php index 8fb6130ae55ec..6201fa083a9fc 100644 --- a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php @@ -22,6 +22,7 @@ 'allow_fallback_to_jetpack_blog_token' => true, 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/7', + 'allow_rest_access' => true, ) ); From 9b85565fb87bf6bf91a2977008ecf2e3de4cf451 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Wed, 18 Sep 2024 14:58:43 +0200 Subject: [PATCH 02/30] Make endpoints provide their own REST route. --- .../changelog/add-json-api-direct-access | 2 +- .../jetpack/class.json-api-endpoints.php | 75 +++++-------------- ...class.wpcom-json-api-get-post-endpoint.php | 4 +- 3 files changed, 24 insertions(+), 57 deletions(-) diff --git a/projects/plugins/jetpack/changelog/add-json-api-direct-access b/projects/plugins/jetpack/changelog/add-json-api-direct-access index c72e6ceb90d66..95c8f04a09752 100644 --- a/projects/plugins/jetpack/changelog/add-json-api-direct-access +++ b/projects/plugins/jetpack/changelog/add-json-api-direct-access @@ -1,4 +1,4 @@ Significance: minor Type: other -Add REST for jsonAPI endpoints. +Add REST support for jsonAPI endpoints. diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 955a64313c063..1b56e96b7e423 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -126,11 +126,11 @@ abstract class WPCOM_JSON_API_Endpoint { public $path_labels = array(); /** - * Use to initialize the REST endpoint accessible directly. + * The REST endpoint if available. * * @var bool */ - public $allow_rest_access = false; + public $rest_route = null; /** * Accepted query parameters @@ -285,6 +285,11 @@ abstract class WPCOM_JSON_API_Endpoint { */ public $allow_fallback_to_jetpack_blog_token = false; + /** + * REST namespace. + */ + const REST_NAMESPACE = 'rest/v1'; + /** * Constructor. * @@ -308,7 +313,7 @@ public function __construct( $args ) { 'new_version' => WPCOM_JSON_API__CURRENT_VERSION, 'jp_disabled' => false, 'path_labels' => array(), - 'allow_rest_access' => false, + 'rest_route' => null, 'request_format' => array(), 'response_format' => array(), 'query_parameters' => array(), @@ -340,14 +345,14 @@ public function __construct( $args ) { $this->force = $args['force']; $this->jp_disabled = $args['jp_disabled']; - $this->method = $args['method']; - $this->path = $args['path']; - $this->path_labels = $args['path_labels']; - $this->allow_rest_access = $args['allow_rest_access']; - $this->min_version = $args['min_version']; - $this->max_version = $args['max_version']; - $this->deprecated = $args['deprecated']; - $this->new_version = $args['new_version']; + $this->method = $args['method']; + $this->path = $args['path']; + $this->path_labels = $args['path_labels']; + $this->rest_route = $args['rest_route']; + $this->min_version = $args['min_version']; + $this->max_version = $args['max_version']; + $this->deprecated = $args['deprecated']; + $this->new_version = $args['new_version']; // Ensure max version is not less than min version. if ( version_compare( $this->min_version, $this->max_version, '>' ) ) { @@ -398,7 +403,7 @@ public function __construct( $args ) { $this->api->add( $this ); - if ( $this->allow_rest_access ) { + if ( $this->rest_route ) { $this->create_rest_route_for_endpoint(); } } @@ -2639,48 +2644,9 @@ public function get_amp_cache_origins( $siteurl ) { * @throws Exception The exception if something goes wrong. */ public function create_rest_route_for_endpoint() { - $route = ''; - $path_parsed = explode( '/', $this->path ); - reset( $this->path_labels ); - - for ( $i = 0, $count = count( $path_parsed ); $i < $count; ++$i ) { - $part = $path_parsed[ $i ]; - - if ( ! $part ) { - continue; - } - - if ( 'sites' === $part ) { - // We don't need the site ID part. - ++$i; - next( $this->path_labels ); - continue; - } - if ( false === strpos( $part, '%' ) ) { - $route .= '/' . $part; - continue; - } - - switch ( $part ) { - case '%s': - $regex = '\w+'; - break; - case '%d': - $regex = '\d+'; - break; - default: - throw new Exception( 'We do not know what to do with this kind of parameter ¯\_(ツ)_/¯' ); - } - - $key = substr( key( $this->path_labels ), 1 ); - next( $this->path_labels ); - - $route .= "/(?P<{$key}>{$regex})"; - } - register_rest_route( - 'wpcom/v1', - $route, + static::REST_NAMESPACE, + $this->rest_route, array( 'methods' => $this->method, 'callback' => array( $this, 'rest_callback' ), @@ -2701,8 +2667,7 @@ public function rest_callback( WP_REST_Request $request ) { return new WP_Error( 'site_not_connected' ); } - $blog_id = \Jetpack_Options::get_option( 'id' ); - + $blog_id = Jetpack_Options::get_option( 'id' ); return call_user_func_array( array( $this, 'callback' ), array_values( array( $this->path, $blog_id ) + $request->get_url_params() ) diff --git a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php index 6201fa083a9fc..3367295cba77f 100644 --- a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php @@ -18,11 +18,11 @@ '$site' => '(int|string) Site ID or domain', '$post_ID' => '(int) The post ID', ), + 'rest_route' => '/posts/(?P\d+)', 'allow_fallback_to_jetpack_blog_token' => true, 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/7', - 'allow_rest_access' => true, ) ); @@ -37,6 +37,7 @@ '$site' => '(int|string) Site ID or domain', '$post_name' => '(string) The post name (a.k.a. slug)', ), + 'rest_route' => '/posts/name/(?P\d+)', 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/name:blogging-and-stuff', ) @@ -55,6 +56,7 @@ '$site' => '(int|string) Site ID or domain', '$post_slug' => '(string) The post slug (a.k.a. sanitized name)', ), + 'rest_route' => '/posts/slug/(?P\d+)', 'allow_fallback_to_jetpack_blog_token' => true, From 36368f0ccc3c71c594840fe08dd11e58586d0cb9 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Wed, 18 Sep 2024 15:03:52 +0200 Subject: [PATCH 03/30] Fix routes for name and slug requests. --- .../class.wpcom-json-api-get-post-endpoint.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php index 3367295cba77f..54177e3e88168 100644 --- a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php @@ -18,7 +18,7 @@ '$site' => '(int|string) Site ID or domain', '$post_ID' => '(int) The post ID', ), - 'rest_route' => '/posts/(?P\d+)', + 'rest_route' => '/posts/(?P\d+)', 'allow_fallback_to_jetpack_blog_token' => true, @@ -37,7 +37,7 @@ '$site' => '(int|string) Site ID or domain', '$post_name' => '(string) The post name (a.k.a. slug)', ), - 'rest_route' => '/posts/name/(?P\d+)', + 'rest_route' => '/posts/name/(?P[\w_-]+)', 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/name:blogging-and-stuff', ) @@ -56,7 +56,7 @@ '$site' => '(int|string) Site ID or domain', '$post_slug' => '(string) The post slug (a.k.a. sanitized name)', ), - 'rest_route' => '/posts/slug/(?P\d+)', + 'rest_route' => '/posts/slug/(?P[\w_-]+)', 'allow_fallback_to_jetpack_blog_token' => true, From 8bfbadbaf0ef2de31cc3d4765bb57dc440ea5151 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Mon, 23 Sep 2024 17:03:58 -0400 Subject: [PATCH 04/30] Add blog/user token verification. --- .../changelog/add-json-api-direct-access | 4 +++ .../src/class-rest-authentication.php | 13 +++++++ .../jetpack/class.json-api-endpoints.php | 35 +++++++++++++++++-- 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 projects/packages/connection/changelog/add-json-api-direct-access diff --git a/projects/packages/connection/changelog/add-json-api-direct-access b/projects/packages/connection/changelog/add-json-api-direct-access new file mode 100644 index 0000000000000..b36404becd080 --- /dev/null +++ b/projects/packages/connection/changelog/add-json-api-direct-access @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add the 'is_signed_with_user_token()' method for REST authentication. diff --git a/projects/packages/connection/src/class-rest-authentication.php b/projects/packages/connection/src/class-rest-authentication.php index 196a0991536ea..d9784cd2c7420 100644 --- a/projects/packages/connection/src/class-rest-authentication.php +++ b/projects/packages/connection/src/class-rest-authentication.php @@ -219,4 +219,17 @@ public static function is_signed_with_blog_token() { return true === $instance->rest_authentication_status && 'blog' === $instance->rest_authentication_type; } + + /** + * Whether the request was signed with a user token. + * + * @since $$next-version$$ + * + * @return bool True if the request was signed with a valid user token, false otherwise. + */ + public static function is_signed_with_user_token() { + $instance = self::init(); + + return true === $instance->rest_authentication_status && 'user' === $instance->rest_authentication_type; + } } diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 1b56e96b7e423..756a3e90c1050 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -7,6 +7,7 @@ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager; +use Automattic\Jetpack\Connection\Rest_Authentication; use Automattic\Jetpack\Status; require_once __DIR__ . '/json-api-config.php'; @@ -288,7 +289,7 @@ abstract class WPCOM_JSON_API_Endpoint { /** * REST namespace. */ - const REST_NAMESPACE = 'rest/v1'; + const REST_NAMESPACE = 'jetpack/rest'; /** * Constructor. @@ -2648,8 +2649,9 @@ public function create_rest_route_for_endpoint() { static::REST_NAMESPACE, $this->rest_route, array( - 'methods' => $this->method, - 'callback' => array( $this, 'rest_callback' ), + 'methods' => $this->method, + 'callback' => array( $this, 'rest_callback' ), + 'permission_callback' => array( $this, 'rest_permission_callback' ), ) ); } @@ -2674,6 +2676,33 @@ public function rest_callback( WP_REST_Request $request ) { ); } + /** + * The REST endpoint should only be available for requests signed with a valid blog or user token. + * Declaring it "final" so individual endpoints couldn't remove this requirement. + * + * @return true|WP_Error + */ + final public function rest_permission_callback() { + if ( Rest_Authentication::is_signed_with_blog_token() || Rest_Authentication::is_signed_with_user_token() ) { + return $this->rest_permission_callback_custom(); + } + + $message = esc_html__( + 'You do not have the correct user permissions to perform this action. Please contact your site admin if you think this is a mistake.', + 'jetpack' + ); + return new WP_Error( 'rest_api_invalid_permission', $message, array( 'status' => rest_authorization_required_code() ) ); + } + + /** + * Redefine in individual endpoint classes to further customize the permission check. + * + * @return true|WP_Error + */ + public function rest_permission_callback_custom() { + return true; + } + /** * Return endpoint response * From 6357e97cce416e3e7b69063508aee72bf1e2aadb Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Mon, 23 Sep 2024 17:07:49 -0400 Subject: [PATCH 05/30] Add the version prefix to the endpoints. --- projects/plugins/jetpack/class.json-api-endpoints.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 756a3e90c1050..a2120df0ff85f 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2645,9 +2645,10 @@ public function get_amp_cache_origins( $siteurl ) { * @throws Exception The exception if something goes wrong. */ public function create_rest_route_for_endpoint() { + $version_prefix = $this->max_version ? 'v' . $this->max_version : ''; register_rest_route( static::REST_NAMESPACE, - $this->rest_route, + $version_prefix . $this->rest_route, array( 'methods' => $this->method, 'callback' => array( $this, 'rest_callback' ), From 9b5e5ae887761cdd2e9a98f309a3dafd067623e3 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Mon, 23 Sep 2024 20:53:41 -0400 Subject: [PATCH 06/30] Authenticate WP user if user token is provided, fix a few smaller auth issues. --- .../jetpack/class.json-api-endpoints.php | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index a2120df0ff85f..0c0b419dcea48 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -404,7 +404,7 @@ public function __construct( $args ) { $this->api->add( $this ); - if ( $this->rest_route ) { + if ( $this->rest_route && ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) ) { $this->create_rest_route_for_endpoint(); } } @@ -2665,12 +2665,11 @@ public function create_rest_route_for_endpoint() { * @return mixed|WP_Error */ public function rest_callback( WP_REST_Request $request ) { - $manager = new Manager( 'jetpack' ); - if ( ! $manager->is_connected() ) { - return new WP_Error( 'site_not_connected' ); - } - $blog_id = Jetpack_Options::get_option( 'id' ); + + $this->api->initialize(); + $this->api->endpoint = $this; + return call_user_func_array( array( $this, 'callback' ), array_values( array( $this->path, $blog_id ) + $request->get_url_params() ) @@ -2684,7 +2683,17 @@ public function rest_callback( WP_REST_Request $request ) { * @return true|WP_Error */ final public function rest_permission_callback() { - if ( Rest_Authentication::is_signed_with_blog_token() || Rest_Authentication::is_signed_with_user_token() ) { + $manager = new Manager( 'jetpack' ); + if ( ! $manager->is_connected() ) { + return new WP_Error( 'site_not_connected' ); + } + + $user_id = Rest_Authentication::init()->wp_rest_authenticate( false ); + if ( $user_id ) { + wp_set_current_user( $user_id ); + } + + if ( ( $this->allow_fallback_to_jetpack_blog_token && Rest_Authentication::is_signed_with_blog_token() ) || Rest_Authentication::is_signed_with_user_token() ) { return $this->rest_permission_callback_custom(); } From 52fe9902c107b2c16fd49295f036d3b18b6a112c Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Wed, 25 Sep 2024 13:18:06 -0400 Subject: [PATCH 07/30] Fix a phpdoc typo. --- projects/plugins/jetpack/class.json-api-endpoints.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 0c0b419dcea48..9414c208c8610 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -129,7 +129,7 @@ abstract class WPCOM_JSON_API_Endpoint { /** * The REST endpoint if available. * - * @var bool + * @var string */ public $rest_route = null; From 549e8260731ae0a91d7ce595b7a88104071bed3e Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Wed, 25 Sep 2024 14:12:51 -0400 Subject: [PATCH 08/30] Activate REST for the '/plugins' endpoint. --- projects/plugins/jetpack/class.jetpack.php | 2 ++ .../jetpack/class.jetpack-json-api-plugins-endpoint.php | 1 - .../jetpack/class.jetpack-json-api-plugins-list-endpoint.php | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/class.jetpack.php b/projects/plugins/jetpack/class.jetpack.php index ab5c7d5e1399f..ba9a9db4e24b2 100644 --- a/projects/plugins/jetpack/class.jetpack.php +++ b/projects/plugins/jetpack/class.jetpack.php @@ -6355,6 +6355,8 @@ public function run_initialize_tracking_action() { public function maybe_initialize_rest_jsonapi() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET['jsonapi'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) { + require_once ABSPATH . 'wp-admin/includes/admin.php'; // JSON API relies on WP functionality not autoloaded in REST. + define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' ); require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php'; } diff --git a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php index c7d8efeb0af57..668066d6889d1 100644 --- a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php @@ -99,7 +99,6 @@ abstract class Jetpack_JSON_API_Plugins_Endpoint extends Jetpack_JSON_API_Endpoi * @return array */ protected function result() { - $plugins = $this->get_plugins(); if ( ! $this->bulk && ! empty( $plugins ) ) { diff --git a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php index 99b7c37f6d72a..7ed7d3240f465 100644 --- a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php @@ -5,6 +5,7 @@ 'description' => 'Get installed Plugins on your blog', 'method' => 'GET', 'path' => '/sites/%s/plugins', + 'rest_route' => '/plugins', 'stat' => 'plugins', 'min_version' => '1', 'max_version' => '1.1', From a3cc27372126d9156f58b0c48b63f8420ee91913 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Thu, 26 Sep 2024 16:52:35 -0400 Subject: [PATCH 09/30] Improve REST authentication. --- .../jetpack/class.json-api-endpoints.php | 23 +++++++++++++------ ...lass.jetpack-json-api-plugins-endpoint.php | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 9414c208c8610..73de4e2609bd0 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2645,10 +2645,9 @@ public function get_amp_cache_origins( $siteurl ) { * @throws Exception The exception if something goes wrong. */ public function create_rest_route_for_endpoint() { - $version_prefix = $this->max_version ? 'v' . $this->max_version : ''; register_rest_route( static::REST_NAMESPACE, - $version_prefix . $this->rest_route, + $this->build_rest_route(), array( 'methods' => $this->method, 'callback' => array( $this, 'rest_callback' ), @@ -2689,12 +2688,17 @@ final public function rest_permission_callback() { } $user_id = Rest_Authentication::init()->wp_rest_authenticate( false ); - if ( $user_id ) { - wp_set_current_user( $user_id ); - } - if ( ( $this->allow_fallback_to_jetpack_blog_token && Rest_Authentication::is_signed_with_blog_token() ) || Rest_Authentication::is_signed_with_user_token() ) { - return $this->rest_permission_callback_custom(); + $allow_blog_token = $this->allow_fallback_to_jetpack_blog_token || $this->allow_jetpack_site_auth; + + if ( ( $allow_blog_token && Rest_Authentication::is_signed_with_blog_token() ) || ( $user_id && Rest_Authentication::is_signed_with_user_token() ) ) { + $success = $this->rest_permission_callback_custom(); + + if ( $success && $user_id ) { + wp_set_current_user( $user_id ); + } + + return $success; } $message = esc_html__( @@ -2713,6 +2717,11 @@ public function rest_permission_callback_custom() { return true; } + public function build_rest_route() { + $version_prefix = $this->max_version ? 'v' . $this->max_version : ''; + return $version_prefix . $this->rest_route; + } + /** * Return endpoint response * diff --git a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php index 668066d6889d1..c7d8efeb0af57 100644 --- a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-endpoint.php @@ -99,6 +99,7 @@ abstract class Jetpack_JSON_API_Plugins_Endpoint extends Jetpack_JSON_API_Endpoi * @return array */ protected function result() { + $plugins = $this->get_plugins(); if ( ! $this->bulk && ! empty( $plugins ) ) { From 4aaa656bf84a13b1b878d8c5ef28723da537389b Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Fri, 11 Oct 2024 15:09:56 -0400 Subject: [PATCH 10/30] Improve custom permission checking. --- .../jetpack/class.json-api-endpoints.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index e0e382cb901bd..e1d51b55b1946 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2692,13 +2692,22 @@ final public function rest_permission_callback() { $allow_blog_token = $this->allow_fallback_to_jetpack_blog_token || $this->allow_jetpack_site_auth; if ( ( $allow_blog_token && Rest_Authentication::is_signed_with_blog_token() ) || ( $user_id && Rest_Authentication::is_signed_with_user_token() ) ) { - $success = $this->rest_permission_callback_custom(); + $custom_permission_result = $this->rest_permission_callback_custom(); - if ( $success && $user_id ) { - wp_set_current_user( $user_id ); + // Successful custom permission check. + if ( $custom_permission_result === true ) { + if ( $user_id ) { + wp_set_current_user( $user_id ); + } + return true; + } + + // Custom permission check errored, returning the error. + if ( is_wp_error( $custom_permission_result ) ) { + return $custom_permission_result; } - return $success; + // Custom permission check failed, but didn't return a specific error. Proceed to returning the generic error. } $message = esc_html__( From 40eec5f060c6bc2daf9a8b2b91440207314380eb Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Fri, 11 Oct 2024 15:10:44 -0400 Subject: [PATCH 11/30] Add missing phpdoc. --- projects/plugins/jetpack/class.json-api-endpoints.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index e1d51b55b1946..1931dfbc06a43 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2726,6 +2726,11 @@ public function rest_permission_callback_custom() { return true; } + /** + * Build the REST endpoint URL. + * + * @return string + */ public function build_rest_route() { $version_prefix = $this->max_version ? 'v' . $this->max_version : ''; return $version_prefix . $this->rest_route; From ab97174feadc8176b57040db7c66496518c000aa Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Thu, 17 Oct 2024 09:08:30 -0400 Subject: [PATCH 12/30] Implement response signature. --- .../jetpack/class.json-api-endpoints.php | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 1931dfbc06a43..30a023d093d8f 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -8,6 +8,7 @@ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager; use Automattic\Jetpack\Connection\Rest_Authentication; +use Automattic\Jetpack\Connection\Tokens; use Automattic\Jetpack\Status; require_once __DIR__ . '/json-api-config.php'; @@ -2669,10 +2670,34 @@ public function rest_callback( WP_REST_Request $request ) { $this->api->initialize(); $this->api->endpoint = $this; - return call_user_func_array( + $response = call_user_func_array( array( $this, 'callback' ), array_values( array( $this->path, $blog_id ) + $request->get_url_params() ) ); + + $token_data = ( new Manager() )->verify_xml_rpc_signature(); + + if ( ! $token_data || empty( $token_data['token_key'] ) || ! array_key_exists( 'user_id', $token_data ) ) { + return new WP_Error( 'response_signature_error' ); + } + + $token = ( new Tokens() )->get_access_token( $token_data['user_id'], $token_data['token_key'] ); + if ( is_wp_error( $token ) ) { + return $token; + } + if ( ! $token ) { + return new WP_Error( 'response_signature_error' ); + } + + $response = wp_json_encode( $response ); + $nonce = wp_generate_password( 10, false ); + $hmac = hash_hmac( 'sha1', $nonce . $response, $token->secret ); + + return array( + $response, + (string) $nonce, + (string) $hmac, + ); } /** From bb92f7a7089ecd295cf2a4036160ee1c063105a9 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Fri, 25 Oct 2024 09:56:05 -0400 Subject: [PATCH 13/30] Make endpoints provide minimum the Jetpack version. --- .../jetpack/class.json-api-endpoints.php | 23 +++++++++++++++++-- ...jetpack-json-api-plugins-list-endpoint.php | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 30a023d093d8f..8b8eacac62106 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -132,7 +132,14 @@ abstract class WPCOM_JSON_API_Endpoint { * * @var string */ - public $rest_route = null; + public $rest_route; + + /** + * Jetpack Version in which REST support was introduced. + * + * @var string + */ + public $rest_min_jp_version; /** * Accepted query parameters @@ -316,6 +323,7 @@ public function __construct( $args ) { 'jp_disabled' => false, 'path_labels' => array(), 'rest_route' => null, + 'rest_min_jp_version' => null, 'request_format' => array(), 'response_format' => array(), 'query_parameters' => array(), @@ -350,12 +358,14 @@ public function __construct( $args ) { $this->method = $args['method']; $this->path = $args['path']; $this->path_labels = $args['path_labels']; - $this->rest_route = $args['rest_route']; $this->min_version = $args['min_version']; $this->max_version = $args['max_version']; $this->deprecated = $args['deprecated']; $this->new_version = $args['new_version']; + $this->rest_route = $args['rest_route']; + $this->rest_min_jp_version = $args['rest_min_jp_version']; + // Ensure max version is not less than min version. if ( version_compare( $this->min_version, $this->max_version, '>' ) ) { $this->max_version = $this->min_version; @@ -2761,6 +2771,15 @@ public function build_rest_route() { return $version_prefix . $this->rest_route; } + /** + * Get Jetpack Version where support for the endpoint was introduced. + * + * @return string + */ + public function get_rest_min_jp_version() { + return $this->rest_min_jp_version; + } + /** * Return endpoint response * diff --git a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php index 7ed7d3240f465..59f8e4f110b7d 100644 --- a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-plugins-list-endpoint.php @@ -6,6 +6,7 @@ 'method' => 'GET', 'path' => '/sites/%s/plugins', 'rest_route' => '/plugins', + 'rest_min_jp_version' => '14.0-a.7', 'stat' => 'plugins', 'min_version' => '1', 'max_version' => '1.1', From 3db7335958f73a96513ae787d7cd9bed563b2c43 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Fri, 25 Oct 2024 11:10:02 -0400 Subject: [PATCH 14/30] Revert Post endpoints changes, we'll focus on the Plugins one for now. --- .../json-endpoints/class.wpcom-json-api-get-post-endpoint.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php index 54177e3e88168..8fb6130ae55ec 100644 --- a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-get-post-endpoint.php @@ -18,7 +18,6 @@ '$site' => '(int|string) Site ID or domain', '$post_ID' => '(int) The post ID', ), - 'rest_route' => '/posts/(?P\d+)', 'allow_fallback_to_jetpack_blog_token' => true, @@ -37,7 +36,6 @@ '$site' => '(int|string) Site ID or domain', '$post_name' => '(string) The post name (a.k.a. slug)', ), - 'rest_route' => '/posts/name/(?P[\w_-]+)', 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/name:blogging-and-stuff', ) @@ -56,7 +54,6 @@ '$site' => '(int|string) Site ID or domain', '$post_slug' => '(string) The post slug (a.k.a. sanitized name)', ), - 'rest_route' => '/posts/slug/(?P[\w_-]+)', 'allow_fallback_to_jetpack_blog_token' => true, From 9b3202fb9d912021211980f9a854a5b4cbbb8a9e Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Wed, 30 Oct 2024 11:26:21 -0400 Subject: [PATCH 15/30] Don't create REST route on WPCOM. --- projects/plugins/jetpack/class.json-api-endpoints.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 8b8eacac62106..ceb55148cd291 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -415,7 +415,7 @@ public function __construct( $args ) { $this->api->add( $this ); - if ( $this->rest_route && ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) ) { + if ( ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) && $this->rest_route && ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) ) { $this->create_rest_route_for_endpoint(); } } From 2e1ae27dd33c9989390c090775e1fb132d5e9944 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Wed, 30 Oct 2024 19:49:55 -0400 Subject: [PATCH 16/30] Remove excessive REST authentication call. --- projects/plugins/jetpack/class.json-api-endpoints.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index ceb55148cd291..8c19f8b5aa1aa 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2722,8 +2722,7 @@ final public function rest_permission_callback() { return new WP_Error( 'site_not_connected' ); } - $user_id = Rest_Authentication::init()->wp_rest_authenticate( false ); - + $user_id = get_current_user_id(); $allow_blog_token = $this->allow_fallback_to_jetpack_blog_token || $this->allow_jetpack_site_auth; if ( ( $allow_blog_token && Rest_Authentication::is_signed_with_blog_token() ) || ( $user_id && Rest_Authentication::is_signed_with_user_token() ) ) { @@ -2731,9 +2730,6 @@ final public function rest_permission_callback() { // Successful custom permission check. if ( $custom_permission_result === true ) { - if ( $user_id ) { - wp_set_current_user( $user_id ); - } return true; } From 8abca0d4b2270b06f5aa124368f26416f0bc3aa8 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Thu, 31 Oct 2024 18:49:36 -0400 Subject: [PATCH 17/30] Remove excessive admin.php loading. --- projects/plugins/jetpack/class-jetpack-xmlrpc-methods.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/projects/plugins/jetpack/class-jetpack-xmlrpc-methods.php b/projects/plugins/jetpack/class-jetpack-xmlrpc-methods.php index 02041eb7593f1..163e7a91f1d23 100644 --- a/projects/plugins/jetpack/class-jetpack-xmlrpc-methods.php +++ b/projects/plugins/jetpack/class-jetpack-xmlrpc-methods.php @@ -176,9 +176,6 @@ public static function json_api( $args = array() ) { define( 'REST_API_REQUEST', true ); define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' ); - // needed? - require_once ABSPATH . 'wp-admin/includes/admin.php'; - require_once JETPACK__PLUGIN_DIR . 'class.json-api.php'; $api = WPCOM_JSON_API::init( $method, $url, $post_body ); $api->token_details['user'] = $user_details; From e97dd90f7a1b60715dfdb530ed220556ed039fa6 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Mon, 4 Nov 2024 13:02:37 -0500 Subject: [PATCH 18/30] Remove excessive fallback to blog token. --- projects/plugins/jetpack/class.json-api-endpoints.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 8c19f8b5aa1aa..911de01e513e8 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2722,10 +2722,7 @@ final public function rest_permission_callback() { return new WP_Error( 'site_not_connected' ); } - $user_id = get_current_user_id(); - $allow_blog_token = $this->allow_fallback_to_jetpack_blog_token || $this->allow_jetpack_site_auth; - - if ( ( $allow_blog_token && Rest_Authentication::is_signed_with_blog_token() ) || ( $user_id && Rest_Authentication::is_signed_with_user_token() ) ) { + if ( ( $this->allow_jetpack_site_auth && Rest_Authentication::is_signed_with_blog_token() ) || ( get_current_user_id() && Rest_Authentication::is_signed_with_user_token() ) ) { $custom_permission_result = $this->rest_permission_callback_custom(); // Successful custom permission check. From 79fdce830c39d958b012d4aec0a1d0d8b571d94e Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Mon, 4 Nov 2024 15:12:43 -0500 Subject: [PATCH 19/30] Improve comments for permission check callbacks. --- projects/plugins/jetpack/class.json-api-endpoints.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 911de01e513e8..06ccbef6c31ef 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2714,6 +2714,10 @@ public function rest_callback( WP_REST_Request $request ) { * The REST endpoint should only be available for requests signed with a valid blog or user token. * Declaring it "final" so individual endpoints couldn't remove this requirement. * + * If you need to add custom permissions to individual endpoints, you can override method `rest_permission_callback_custom()`. + * + * @see self::rest_permission_callback_custom() + * * @return true|WP_Error */ final public function rest_permission_callback() { @@ -2746,7 +2750,10 @@ final public function rest_permission_callback() { } /** - * Redefine in individual endpoint classes to further customize the permission check. + * You can override this method in individual endpoints to add custom permission checks. + * This will run on top of `rest_permission_callback()`. + * + * @see self::rest_permission_callback() * * @return true|WP_Error */ From 22e01d72c5056d862ba6b28c6655f51f3575b913 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Thu, 7 Nov 2024 17:01:46 -0500 Subject: [PATCH 20/30] Copy some code from XML-RPC's 'serve'. --- projects/plugins/jetpack/class.json-api-endpoints.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 06ccbef6c31ef..6627bf5c163fc 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2675,11 +2675,21 @@ public function create_rest_route_for_endpoint() { * @return mixed|WP_Error */ public function rest_callback( WP_REST_Request $request ) { + // phpcs:ignore WordPress.PHP.IniSet.display_errors_Disallowed -- Making sure random warnings don't break JSON. + ini_set( 'display_errors', false ); + $blog_id = Jetpack_Options::get_option( 'id' ); $this->api->initialize(); $this->api->endpoint = $this; + if ( $this->in_testing && ! WPCOM_JSON_API__DEBUG ) { + return new WP_Error( 'endpoint_not_available' ); + } + + /** This action is documented in class.json-api.php */ + do_action( 'wpcom_json_api_output', $this->stat ); + $response = call_user_func_array( array( $this, 'callback' ), array_values( array( $this->path, $blog_id ) + $request->get_url_params() ) From 01a8fbc4c975d28e9ad5d12a3efe5de05d0da673 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Tue, 12 Nov 2024 19:40:17 -0500 Subject: [PATCH 21/30] Handle locale. --- .../jetpack/class-jetpack-xmlrpc-methods.php | 24 ++-------------- .../jetpack/class.json-api-endpoints.php | 5 ++++ projects/plugins/jetpack/class.json-api.php | 28 +++++++++++++++++++ 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/projects/plugins/jetpack/class-jetpack-xmlrpc-methods.php b/projects/plugins/jetpack/class-jetpack-xmlrpc-methods.php index 163e7a91f1d23..4dd2b1f07c45d 100644 --- a/projects/plugins/jetpack/class-jetpack-xmlrpc-methods.php +++ b/projects/plugins/jetpack/class-jetpack-xmlrpc-methods.php @@ -137,27 +137,6 @@ public static function json_api( $args = array() ) { } } - if ( 'en' !== $locale ) { - // .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them. - $new_locale = $locale; - if ( str_contains( $locale, '-' ) ) { - $locale_pieces = explode( '-', $locale ); - $new_locale = $locale_pieces[0]; - $new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : ''; - } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found - // .com might pass 'fr' because thats what our language files are named as, where core seems - // to do fr_FR - so try that if we don't think we can load the file. - if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) { - $new_locale = $locale . '_' . strtoupper( $locale ); - } - } - - if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) { - unload_textdomain( 'default' ); - load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' ); - } - } - $old_user = wp_get_current_user(); wp_set_current_user( $user_id ); @@ -179,6 +158,9 @@ public static function json_api( $args = array() ) { require_once JETPACK__PLUGIN_DIR . 'class.json-api.php'; $api = WPCOM_JSON_API::init( $method, $url, $post_body ); $api->token_details['user'] = $user_details; + + $api->init_locale( $locale ); + require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php'; $display_errors = ini_set( 'display_errors', 0 ); // phpcs:ignore WordPress.PHP.IniSet diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 6627bf5c163fc..f6b9c09f3762c 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2683,6 +2683,11 @@ public function rest_callback( WP_REST_Request $request ) { $this->api->initialize(); $this->api->endpoint = $this; + $locale = $request->get_param( 'language' ); + if ( $locale ) { + $this->api->init_locale( $locale ); + } + if ( $this->in_testing && ! WPCOM_JSON_API__DEBUG ) { return new WP_Error( 'endpoint_not_available' ); } diff --git a/projects/plugins/jetpack/class.json-api.php b/projects/plugins/jetpack/class.json-api.php index 6b9a805c6ea4a..1ac8f4473fae2 100644 --- a/projects/plugins/jetpack/class.json-api.php +++ b/projects/plugins/jetpack/class.json-api.php @@ -1271,4 +1271,32 @@ public function finish_request() { return fastcgi_finish_request(); } } + + /** + * Initialize the locale if different from 'en'. + * + * @param string $locale The locale to initialize. + */ + public function init_locale( $locale ) { + if ( 'en' !== $locale ) { + // .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them. + $new_locale = $locale; + if ( str_contains( $locale, '-' ) ) { + $locale_pieces = explode( '-', $locale ); + $new_locale = $locale_pieces[0]; + $new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : ''; + } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found + // .com might pass 'fr' because thats what our language files are named as, where core seems + // to do fr_FR - so try that if we don't think we can load the file. + if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) { + $new_locale = $locale . '_' . strtoupper( $locale ); + } + } + + if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) { + unload_textdomain( 'default' ); + load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' ); + } + } + } } From 801367314919532cae32c9cba003b6f5a0bc9da7 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Tue, 19 Nov 2024 17:11:49 -0500 Subject: [PATCH 22/30] Properly wrap REST JSON API errors. --- .../plugins/jetpack/class.json-api-endpoints.php | 10 +++++++++- projects/plugins/jetpack/class.json-api.php | 13 ++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index f6b9c09f3762c..68c8aff7492ca 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2695,11 +2695,19 @@ public function rest_callback( WP_REST_Request $request ) { /** This action is documented in class.json-api.php */ do_action( 'wpcom_json_api_output', $this->stat ); - $response = call_user_func_array( + $callback_response = call_user_func_array( array( $this, 'callback' ), array_values( array( $this->path, $blog_id ) + $request->get_url_params() ) ); + if ( ! $callback_response && ! is_array( $callback_response ) ) { + $response = $this->api->output( 500, '', 'text/plain' ); + } elseif ( is_wp_error( $callback_response ) ) { + $response = $this->api->output_error( $callback_response ); + } else { + $this->api->output( $$this->api->output_status_code, $callback_response, 'application/json' ); + } + $token_data = ( new Manager() )->verify_xml_rpc_signature(); if ( ! $token_data || empty( $token_data['token_key'] ) || ! array_key_exists( 'user_id', $token_data ) ) { diff --git a/projects/plugins/jetpack/class.json-api.php b/projects/plugins/jetpack/class.json-api.php index 1ac8f4473fae2..fe090c7f70d08 100644 --- a/projects/plugins/jetpack/class.json-api.php +++ b/projects/plugins/jetpack/class.json-api.php @@ -646,9 +646,10 @@ public function set_output_status_code( $code = 200 ) { * @param mixed $response Response data. * @param string $content_type Content type of the response. * @param array $extra Additional HTTP headers. - * @return string Content type (assuming it didn't exit). + * @param bool $return_response Return data instead of outputting it directly. + * @return string Content type (assuming it didn't exit), or the response if $return_response set to true. */ - public function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) { + public function output( $status_code, $response = null, $content_type = 'application/json', $extra = array(), $return_response = false ) { $status_code = (int) $status_code; // In case output() was called before the callback returned. @@ -716,6 +717,12 @@ public function output( $status_code, $response = null, $content_type = 'applica $content_type = 'application/json'; } + $encoded_response = $this->json_encode( $response ); + + if ( $return_response ) { + return $encoded_response; + } + status_header( (int) $status_code ); header( "Content-Type: $content_type" ); if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) { @@ -731,7 +738,7 @@ public function output( $status_code, $response = null, $content_type = 'applica echo "/**/$callback("; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSONP output, not HTML. } - echo $this->json_encode( $response ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSON or JSONP output, not HTML. + echo $encoded_response; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSON or JSONP output, not HTML. if ( $callback ) { echo ');'; } From b2b467a03afe9ef750a28403e04decdfcd8b80e8 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Wed, 20 Nov 2024 16:32:30 -0500 Subject: [PATCH 23/30] Properly wrap REST response. --- .../plugins/jetpack/class.json-api-endpoints.php | 12 ++++++------ projects/plugins/jetpack/class.json-api.php | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 68c8aff7492ca..89d1014e0df7c 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2701,11 +2701,12 @@ public function rest_callback( WP_REST_Request $request ) { ); if ( ! $callback_response && ! is_array( $callback_response ) ) { - $response = $this->api->output( 500, '', 'text/plain' ); + $response = $this->api->output( 500, '', 'text/plain', array(), false ); } elseif ( is_wp_error( $callback_response ) ) { - $response = $this->api->output_error( $callback_response ); + $error = WPCOM_JSON_API::serializable_error( $callback_response ); + return new WP_Error( $error['errors']['error'], $error['errors']['message'], array( 'status' => $error['status_code'] ) ); } else { - $this->api->output( $$this->api->output_status_code, $callback_response, 'application/json' ); + $response = $this->api->output( $this->api->output_status_code, $callback_response, 'application/json', array(), true ); } $token_data = ( new Manager() )->verify_xml_rpc_signature(); @@ -2722,9 +2723,8 @@ public function rest_callback( WP_REST_Request $request ) { return new WP_Error( 'response_signature_error' ); } - $response = wp_json_encode( $response ); - $nonce = wp_generate_password( 10, false ); - $hmac = hash_hmac( 'sha1', $nonce . $response, $token->secret ); + $nonce = wp_generate_password( 10, false ); + $hmac = hash_hmac( 'sha1', $nonce . $response, $token->secret ); return array( $response, diff --git a/projects/plugins/jetpack/class.json-api.php b/projects/plugins/jetpack/class.json-api.php index fe090c7f70d08..8b735da63767f 100644 --- a/projects/plugins/jetpack/class.json-api.php +++ b/projects/plugins/jetpack/class.json-api.php @@ -659,7 +659,10 @@ public function output( $status_code, $response = null, $content_type = 'applica } return $content_type; } - $this->did_output = true; + + if ( ! $return_response ) { + $this->did_output = true; + } // 400s and 404s are allowed for all origins if ( 404 === $status_code || 400 === $status_code ) { From eb68e2c11978cd4ee5cc97ff023f6d8a3383b726 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Thu, 21 Nov 2024 10:50:48 -0500 Subject: [PATCH 24/30] Fix phan error. --- projects/plugins/jetpack/.phan/baseline.php | 4 ++-- projects/plugins/jetpack/class.json-api-endpoints.php | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/projects/plugins/jetpack/.phan/baseline.php b/projects/plugins/jetpack/.phan/baseline.php index 32c0d1270c42a..9e2c3629499d0 100644 --- a/projects/plugins/jetpack/.phan/baseline.php +++ b/projects/plugins/jetpack/.phan/baseline.php @@ -56,11 +56,11 @@ // PhanTypeMismatchArgumentInternalReal : 7 occurrences // PhanCommentAbstractOnInheritedMethod : 6 occurrences // PhanDeprecatedClass : 5 occurrences + // PhanImpossibleCondition : 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 @@ -170,7 +170,7 @@ 'class.jetpack-post-images.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanTypeMismatchArgument', 'PhanTypeMismatchDefault', 'PhanTypeMismatchReturnProbablyReal'], 'class.jetpack-twitter-cards.php' => ['PhanPluginSimplifyExpressionBool', 'PhanRedundantCondition', 'PhanTypeArraySuspiciousNullable', 'PhanTypeMismatchArgument', 'PhanTypePossiblyInvalidDimOffset'], 'class.jetpack.php' => ['PhanAccessMethodInternal', 'PhanDeprecatedFunction', 'PhanNoopNew', 'PhanPluginDuplicateConditionalNullCoalescing', 'PhanPossiblyUndeclaredVariable', 'PhanRedundantConditionInLoop', 'PhanTypeArraySuspiciousNullable', 'PhanTypeExpectedObjectPropAccess', 'PhanTypeMismatchArgument', 'PhanTypeMismatchArgumentNullableInternal', 'PhanTypeMismatchArgumentProbablyReal', 'PhanTypeMismatchDefault', 'PhanTypeMismatchPropertyDefault', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal'], - 'class.json-api-endpoints.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanPluginSimplifyExpressionBool', 'PhanPossiblyUndeclaredVariable', 'PhanRedundantCondition', 'PhanTypeArraySuspiciousNullable', 'PhanTypeComparisonToArray', 'PhanTypeMismatchArgument', 'PhanTypeMismatchArgumentInternal', 'PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchArgumentProbablyReal', 'PhanTypeMismatchReturnProbablyReal', 'PhanUndeclaredProperty'], + 'class.json-api-endpoints.php' => ['PhanImpossibleCondition', 'PhanPluginDuplicateConditionalNullCoalescing', 'PhanPluginSimplifyExpressionBool', 'PhanPossiblyUndeclaredVariable', 'PhanRedundantCondition', 'PhanTypeArraySuspiciousNullable', 'PhanTypeComparisonToArray', 'PhanTypeMismatchArgument', 'PhanTypeMismatchArgumentInternal', 'PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchArgumentProbablyReal', 'PhanTypeMismatchReturnProbablyReal', 'PhanUndeclaredProperty'], 'class.json-api.php' => ['PhanPluginDuplicateSwitchCaseLooseEquality', 'PhanPluginSimplifyExpressionBool', 'PhanPossiblyUndeclaredVariable', 'PhanRedundantCondition', 'PhanTypeArraySuspicious', 'PhanTypeArraySuspiciousNullable', 'PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchProperty', 'PhanTypeMismatchPropertyDefault', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal'], 'enhanced-open-graph.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanTypeArraySuspiciousNullable'], 'extensions/blocks/ai-chat/ai-chat.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanTypeMismatchArgument'], diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 89d1014e0df7c..438f126cd5e81 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2701,6 +2701,7 @@ public function rest_callback( WP_REST_Request $request ) { ); if ( ! $callback_response && ! is_array( $callback_response ) ) { + // Dealing with empty non-array response. Phan is wrong about it being an "impossible condition". $response = $this->api->output( 500, '', 'text/plain', array(), false ); } elseif ( is_wp_error( $callback_response ) ) { $error = WPCOM_JSON_API::serializable_error( $callback_response ); From bbb226a966c98d9076c2f07b2ed3307236a1d03f Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Tue, 3 Dec 2024 10:54:31 -0500 Subject: [PATCH 25/30] Fix the empty output response. --- projects/plugins/jetpack/class.json-api-endpoints.php | 2 +- projects/plugins/jetpack/class.json-api.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index 438f126cd5e81..c6cb2909324b7 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2702,7 +2702,7 @@ public function rest_callback( WP_REST_Request $request ) { if ( ! $callback_response && ! is_array( $callback_response ) ) { // Dealing with empty non-array response. Phan is wrong about it being an "impossible condition". - $response = $this->api->output( 500, '', 'text/plain', array(), false ); + $response = $this->api->output( 500, '', 'text/plain', array(), true ); } elseif ( is_wp_error( $callback_response ) ) { $error = WPCOM_JSON_API::serializable_error( $callback_response ); return new WP_Error( $error['errors']['error'], $error['errors']['message'], array( 'status' => $error['status_code'] ) ); diff --git a/projects/plugins/jetpack/class.json-api.php b/projects/plugins/jetpack/class.json-api.php index 8b735da63767f..b1abd02daeb13 100644 --- a/projects/plugins/jetpack/class.json-api.php +++ b/projects/plugins/jetpack/class.json-api.php @@ -584,6 +584,7 @@ public function serve( $exit = true ) { do_action( 'wpcom_json_api_output', $endpoint->stat ); $response = $this->process_request( $endpoint, $path_pieces ); + $response = null; if ( ! $response && ! is_array( $response ) ) { return $this->output( 500, '', 'text/plain' ); @@ -686,6 +687,11 @@ public function output( $status_code, $response = null, $content_type = 'applica foreach ( $extra as $key => $value ) { header( "$key: $value" ); } + + if ( $return_response ) { + return $response; + } + echo $response; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if ( $this->exit ) { exit; From debee21ff92681e2deb81b0496ac82c5e84092cd Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Tue, 3 Dec 2024 11:06:17 -0500 Subject: [PATCH 26/30] Revert accidental debugging code commit. --- projects/plugins/jetpack/class.json-api.php | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/plugins/jetpack/class.json-api.php b/projects/plugins/jetpack/class.json-api.php index b1abd02daeb13..204789dadd72f 100644 --- a/projects/plugins/jetpack/class.json-api.php +++ b/projects/plugins/jetpack/class.json-api.php @@ -584,7 +584,6 @@ public function serve( $exit = true ) { do_action( 'wpcom_json_api_output', $endpoint->stat ); $response = $this->process_request( $endpoint, $path_pieces ); - $response = null; if ( ! $response && ! is_array( $response ) ) { return $this->output( 500, '', 'text/plain' ); From 2b2ff8c036688058c9a8e2b198ef91468d7a41f9 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Thu, 12 Dec 2024 19:54:39 -0500 Subject: [PATCH 27/30] Proper response wrapping and error handling. --- .../jetpack/class.json-api-endpoints.php | 46 ++++++++++------ projects/plugins/jetpack/class.json-api.php | 53 ++++++++++++------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api-endpoints.php b/projects/plugins/jetpack/class.json-api-endpoints.php index c6cb2909324b7..8dfeddff2693b 100644 --- a/projects/plugins/jetpack/class.json-api-endpoints.php +++ b/projects/plugins/jetpack/class.json-api-endpoints.php @@ -2692,36 +2692,48 @@ public function rest_callback( WP_REST_Request $request ) { return new WP_Error( 'endpoint_not_available' ); } + $token_data = ( new Manager() )->verify_xml_rpc_signature(); + if ( ! $token_data || empty( $token_data['token_key'] ) || ! array_key_exists( 'user_id', $token_data ) ) { + return new WP_Error( 'response_signature_error' ); + } + + $token = ( new Tokens() )->get_access_token( $token_data['user_id'], $token_data['token_key'] ); + if ( is_wp_error( $token ) ) { + return $token; + } + if ( ! $token ) { + return new WP_Error( 'response_signature_error' ); + } + /** This action is documented in class.json-api.php */ do_action( 'wpcom_json_api_output', $this->stat ); - $callback_response = call_user_func_array( + $response = call_user_func_array( array( $this, 'callback' ), array_values( array( $this->path, $blog_id ) + $request->get_url_params() ) ); - if ( ! $callback_response && ! is_array( $callback_response ) ) { + if ( ! $response && ! is_array( $response ) ) { // Dealing with empty non-array response. Phan is wrong about it being an "impossible condition". - $response = $this->api->output( 500, '', 'text/plain', array(), true ); - } elseif ( is_wp_error( $callback_response ) ) { - $error = WPCOM_JSON_API::serializable_error( $callback_response ); - return new WP_Error( $error['errors']['error'], $error['errors']['message'], array( 'status' => $error['status_code'] ) ); - } else { - $response = $this->api->output( $this->api->output_status_code, $callback_response, 'application/json', array(), true ); + $response = new WP_Error( 'empty_response', 'Endpoint response is empty', 500 ); } - $token_data = ( new Manager() )->verify_xml_rpc_signature(); + $status_code = 200; - if ( ! $token_data || empty( $token_data['token_key'] ) || ! array_key_exists( 'user_id', $token_data ) ) { - return new WP_Error( 'response_signature_error' ); - } + if ( is_wp_error( $response ) ) { + $status_code = 500; - $token = ( new Tokens() )->get_access_token( $token_data['user_id'], $token_data['token_key'] ); - if ( is_wp_error( $token ) ) { - return $token; + if ( $response->get_error_data() && is_scalar( $response->get_error_data() ) + && (string) (int) $response->get_error_data() === (string) $response->get_error_data() + ) { + $status_code = (int) $response->get_error_data(); + } + + $response = WPCOM_JSON_API::serializable_error( $response ); } - if ( ! $token ) { - return new WP_Error( 'response_signature_error' ); + + if ( $request->get_param( 'http_envelope' ) ) { + $response = wp_json_encode( WPCOM_JSON_API::wrap_http_envelope( $status_code, $response, 'application/json' ) ); } $nonce = wp_generate_password( 10, false ); diff --git a/projects/plugins/jetpack/class.json-api.php b/projects/plugins/jetpack/class.json-api.php index 204789dadd72f..b4795b0d2f2f4 100644 --- a/projects/plugins/jetpack/class.json-api.php +++ b/projects/plugins/jetpack/class.json-api.php @@ -702,25 +702,8 @@ public function output( $status_code, $response = null, $content_type = 'applica $response = $this->filter_fields( $response ); if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) { - $headers = array( - array( - 'name' => 'Content-Type', - 'value' => $content_type, - ), - ); + $response = static::wrap_http_envelope( $status_code, $response, $content_type, $extra ); - foreach ( $extra as $key => $value ) { - $headers[] = array( - 'name' => $key, - 'value' => $value, - ); - } - - $response = array( - 'code' => (int) $status_code, - 'headers' => $headers, - 'body' => $response, - ); $status_code = 200; $content_type = 'application/json'; } @@ -758,6 +741,40 @@ public function output( $status_code, $response = null, $content_type = 'applica return $content_type; } + /** + * Wrap JSON API response into an HTTP 200 one. + * + * @param int $status_code HTTP status code. + * @param string $response Response body. + * @param string $content_type Content type. + * @param array|null $extra Extra data. + * + * @return array + */ + public static function wrap_http_envelope( $status_code, $response, $content_type, $extra = null ) { + $headers = array( + array( + 'name' => 'Content-Type', + 'value' => $content_type, + ), + ); + + if ( is_array( $extra ) ) { + foreach ( $extra as $key => $value ) { + $headers[] = array( + 'name' => $key, + 'value' => $value, + ); + } + } + + return array( + 'code' => (int) $status_code, + 'headers' => $headers, + 'body' => $response, + ); + } + /** * Serialize an error. * From 9d6f78a30ab71897f27800aeb89fbf1faaf70117 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Thu, 12 Dec 2024 20:02:19 -0500 Subject: [PATCH 28/30] Revert changes to the 'output()' method. --- projects/plugins/jetpack/class.json-api.php | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api.php b/projects/plugins/jetpack/class.json-api.php index b4795b0d2f2f4..4b05c712c645a 100644 --- a/projects/plugins/jetpack/class.json-api.php +++ b/projects/plugins/jetpack/class.json-api.php @@ -646,10 +646,9 @@ public function set_output_status_code( $code = 200 ) { * @param mixed $response Response data. * @param string $content_type Content type of the response. * @param array $extra Additional HTTP headers. - * @param bool $return_response Return data instead of outputting it directly. - * @return string Content type (assuming it didn't exit), or the response if $return_response set to true. + * @return string Content type if the function didn't exit */ - public function output( $status_code, $response = null, $content_type = 'application/json', $extra = array(), $return_response = false ) { + public function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) { $status_code = (int) $status_code; // In case output() was called before the callback returned. @@ -660,10 +659,6 @@ public function output( $status_code, $response = null, $content_type = 'applica return $content_type; } - if ( ! $return_response ) { - $this->did_output = true; - } - // 400s and 404s are allowed for all origins if ( 404 === $status_code || 400 === $status_code ) { header( 'Access-Control-Allow-Origin: *' ); @@ -687,10 +682,6 @@ public function output( $status_code, $response = null, $content_type = 'applica header( "$key: $value" ); } - if ( $return_response ) { - return $response; - } - echo $response; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if ( $this->exit ) { exit; @@ -708,12 +699,6 @@ public function output( $status_code, $response = null, $content_type = 'applica $content_type = 'application/json'; } - $encoded_response = $this->json_encode( $response ); - - if ( $return_response ) { - return $encoded_response; - } - status_header( (int) $status_code ); header( "Content-Type: $content_type" ); if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) { @@ -729,7 +714,7 @@ public function output( $status_code, $response = null, $content_type = 'applica echo "/**/$callback("; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSONP output, not HTML. } - echo $encoded_response; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSON or JSONP output, not HTML. + echo $this->json_encode( $response ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is JSON or JSONP output, not HTML. if ( $callback ) { echo ');'; } From 9739eaad906d9f55f77bc9319c3ea027ce8a95a7 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Thu, 12 Dec 2024 20:04:21 -0500 Subject: [PATCH 29/30] Mix some reverting issues. --- projects/plugins/jetpack/class.json-api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/plugins/jetpack/class.json-api.php b/projects/plugins/jetpack/class.json-api.php index 4b05c712c645a..9ecb996cf88e3 100644 --- a/projects/plugins/jetpack/class.json-api.php +++ b/projects/plugins/jetpack/class.json-api.php @@ -646,7 +646,7 @@ public function set_output_status_code( $code = 200 ) { * @param mixed $response Response data. * @param string $content_type Content type of the response. * @param array $extra Additional HTTP headers. - * @return string Content type if the function didn't exit + * @return string Content type (assuming it didn't exit). */ public function output( $status_code, $response = null, $content_type = 'application/json', $extra = array() ) { $status_code = (int) $status_code; @@ -658,6 +658,7 @@ public function output( $status_code, $response = null, $content_type = 'applica } return $content_type; } + $this->did_output = true; // 400s and 404s are allowed for all origins if ( 404 === $status_code || 400 === $status_code ) { @@ -681,7 +682,6 @@ public function output( $status_code, $response = null, $content_type = 'applica foreach ( $extra as $key => $value ) { header( "$key: $value" ); } - echo $response; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if ( $this->exit ) { exit; From 4850259ca3be16ac70c8a3653bf27f59170f21e1 Mon Sep 17 00:00:00 2001 From: sergeymitr Date: Thu, 12 Dec 2024 20:19:59 -0500 Subject: [PATCH 30/30] Fix a type mismatch. --- projects/plugins/jetpack/class.json-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/class.json-api.php b/projects/plugins/jetpack/class.json-api.php index 9ecb996cf88e3..85b0b78d32a16 100644 --- a/projects/plugins/jetpack/class.json-api.php +++ b/projects/plugins/jetpack/class.json-api.php @@ -730,7 +730,7 @@ public function output( $status_code, $response = null, $content_type = 'applica * Wrap JSON API response into an HTTP 200 one. * * @param int $status_code HTTP status code. - * @param string $response Response body. + * @param mixed $response Response body. * @param string $content_type Content type. * @param array|null $extra Extra data. *