From 863eb917d3c79fe9d6544fb262ccae61a7322f8b Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Wed, 11 Dec 2024 10:01:15 +0530 Subject: [PATCH 1/8] Create Services_Controller class --- .../class-services-controller.php | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 projects/packages/publicize/src/rest-endpoints/class-services-controller.php diff --git a/projects/packages/publicize/src/rest-endpoints/class-services-controller.php b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php new file mode 100644 index 0000000000000..ad366f0ec89ec --- /dev/null +++ b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php @@ -0,0 +1,213 @@ +namespace = 'wpcom/v3'; + $this->rest_base = 'publicize/services'; + + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + + $this->is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM; + + $this->wpcom_is_wpcom_only_endpoint = true; + } + + /** + * Register the routes. + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permission_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Schema for the endpoint. + * + * @return array + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'jetpack-publicize-connection', + 'type' => 'object', + 'properties' => array( + 'ID' => array( + 'type' => 'string', + 'description' => __( 'Alphanumeric identifier for the service.', 'jetpack-publicize-pkg' ), + ), + 'label' => array( + 'type' => 'string', + 'description' => __( 'Human-readable label for the Jetpack Social service.', 'jetpack-publicize-pkg' ), + ), + 'type' => array( + 'type' => 'string', + 'description' => __( 'Type of service.', 'jetpack-publicize-pkg' ), + 'enum' => array( + 'publicize', + 'other', + ), + ), + 'description' => array( + 'type' => 'string', + 'description' => __( 'Description for the service.', 'jetpack-publicize-pkg' ), + ), + 'connect_URL' => array( + 'type' => 'string', + 'description' => __( 'URL to use for connecting an account for the service.', 'jetpack-publicize-pkg' ), + ), + 'external_users_only' => array( + 'type' => 'boolean', + 'description' => __( 'Whether the service supports only the external users and not the main user account.', 'jetpack-publicize-pkg' ), + ), + 'multiple_external_user_ID_support' => array( + 'type' => 'boolean', + 'description' => __( 'Whether the service is supported for multiple external user accounts.', 'jetpack-publicize-pkg' ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } + + /** + * Get list of connected Publicize connections. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_REST_Response suitable for 1-page collection + */ + public function get_items( $request ) { + if ( $this->is_wpcom ) { + if ( function_exists( 'require_lib' ) ) { + // @phan-suppress-next-line PhanUndeclaredFunction - phan is dumb not to see the function_exists check. + require_lib( 'external-connections' ); + } + + // @phan-suppress-next-line PhanUndeclaredClassMethod - We are here because we are on WPCOM. + $external_connections = \WPCOM_External_Connections::init(); + + $services = $external_connections->get_external_services_list( 'publicize', get_current_blog_id() ); + + $services = array_values( $services ); + + return rest_ensure_response( $services ); + + } else { + $site_id = Manager::get_site_id( true ); + if ( ! $site_id ) { + return rest_ensure_response( array() ); + } + + $path = add_query_arg( + $request->get_query_params(), + sprintf( '/sites/%d/' . $this->rest_base, $site_id ) + ); + + $response = Client::wpcom_json_api_request_as_user( $path, 'v3', array( 'method' => 'GET' ) ); + + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + // TODO log error. + return rest_ensure_response( array() ); + } + + return rest_ensure_response( + json_decode( wp_remote_retrieve_body( $response ), true ) + ); + } + } + + /** + * Filters out data based on ?_fields= request parameter + * + * @param array $item Item to prepare. + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_REST_Response filtered item + */ + public function prepare_item_for_response( $item, $request ) { + if ( ! is_callable( array( $this, 'get_fields_for_response' ) ) ) { + return rest_ensure_response( $item ); + } + + $fields = $this->get_fields_for_response( $request ); + + $response_data = array(); + foreach ( $item as $field => $value ) { + if ( in_array( $field, $fields, true ) ) { + $response_data[ $field ] = $value; + } + } + + return rest_ensure_response( $response_data ); + } + + /** + * Verify that user can access Publicize data + * + * @return true|WP_Error + */ + public function get_items_permission_check() { + global $publicize; + + if ( ! $publicize ) { + return new WP_Error( + 'publicize_not_available', + __( 'Sorry, Jetpack Social is not available on your site right now.', 'jetpack-publicize-pkg' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + if ( $publicize->current_user_can_access_publicize_data() ) { + return true; + } + + return new WP_Error( + 'invalid_user_permission_publicize', + __( 'Sorry, you are not allowed to access Jetpack Social data on this site.', 'jetpack-publicize-pkg' ), + array( 'status' => rest_authorization_required_code() ) + ); + } +} + +if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + wpcom_rest_api_v2_load_plugin( Services_Controller::class ); +} From a81c5e34dbc5235efd6fb719276987c06c092061 Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Wed, 11 Dec 2024 10:01:25 +0530 Subject: [PATCH 2/8] Initialize Services_Controller --- projects/packages/publicize/src/class-publicize-assets.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects/packages/publicize/src/class-publicize-assets.php b/projects/packages/publicize/src/class-publicize-assets.php index f3e718ae6b9f3..899222d39ce9d 100644 --- a/projects/packages/publicize/src/class-publicize-assets.php +++ b/projects/packages/publicize/src/class-publicize-assets.php @@ -8,6 +8,7 @@ namespace Automattic\Jetpack\Publicize; use Automattic\Jetpack\Publicize\Rest_Endpoints\Connections_Controller; +use Automattic\Jetpack\Publicize\Rest_Endpoints\Services_Controller; /** * Publicize_Assets class. @@ -20,5 +21,6 @@ class Publicize_Assets { public static function configure() { Publicize_Script_Data::configure(); new Connections_Controller(); + new Services_Controller(); } } From 69c4d2e5abe7ebb5fd6ce0f6a35ce03bd2a3f88b Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Wed, 11 Dec 2024 10:01:37 +0530 Subject: [PATCH 3/8] Phan is not my fan --- projects/packages/publicize/.phan/baseline.php | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/packages/publicize/.phan/baseline.php b/projects/packages/publicize/.phan/baseline.php index e6d2be7e491bc..1a2adf70b249b 100644 --- a/projects/packages/publicize/.phan/baseline.php +++ b/projects/packages/publicize/.phan/baseline.php @@ -39,6 +39,7 @@ 'src/class-publicize.php' => ['PhanParamSignatureMismatch', 'PhanPossiblyUndeclaredVariable', 'PhanTypeMismatchArgument', 'PhanTypeMissingReturn'], 'src/class-rest-controller.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanTypeMismatchReturnProbablyReal'], 'src/rest-endpoints/class-connections-controller.php' => ['PhanPluginMixedKeyNoKey'], + 'src/rest-endpoints/class-services-controller.php' => ['PhanPluginMixedKeyNoKey'], 'src/social-image-generator/class-post-settings.php' => ['PhanPluginDuplicateConditionalNullCoalescing'], 'src/social-image-generator/class-rest-settings-controller.php' => ['PhanPluginMixedKeyNoKey'], 'src/social-image-generator/class-settings.php' => ['PhanPluginDuplicateConditionalNullCoalescing'], From 215a01b95a0a43dfa8895c844975325d89548794 Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Wed, 11 Dec 2024 10:22:07 +0530 Subject: [PATCH 4/8] Improve filtering of fields --- .../class-services-controller.php | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/projects/packages/publicize/src/rest-endpoints/class-services-controller.php b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php index ad366f0ec89ec..18e37df289cee 100644 --- a/projects/packages/publicize/src/rest-endpoints/class-services-controller.php +++ b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php @@ -109,14 +109,14 @@ public function get_item_schema() { } /** - * Get list of connected Publicize connections. + * Get a list of Publicize supported services. * - * @param WP_REST_Request $request Full details about the request. + * @param bool $is_wpcom Whether we are on WPCOM. * - * @return WP_REST_Response suitable for 1-page collection + * @return array */ - public function get_items( $request ) { - if ( $this->is_wpcom ) { + public static function get_supported_services( $is_wpcom = false ) { + if ( $is_wpcom ) { if ( function_exists( 'require_lib' ) ) { // @phan-suppress-next-line PhanUndeclaredFunction - phan is dumb not to see the function_exists check. require_lib( 'external-connections' ); @@ -129,30 +129,52 @@ public function get_items( $request ) { $services = array_values( $services ); - return rest_ensure_response( $services ); + return $services; } else { $site_id = Manager::get_site_id( true ); if ( ! $site_id ) { - return rest_ensure_response( array() ); + return array(); } - $path = add_query_arg( - $request->get_query_params(), - sprintf( '/sites/%d/' . $this->rest_base, $site_id ) - ); + $path = sprintf( '/sites/%d/publicize/services', $site_id ); $response = Client::wpcom_json_api_request_as_user( $path, 'v3', array( 'method' => 'GET' ) ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { // TODO log error. - return rest_ensure_response( array() ); + return array(); } - return rest_ensure_response( - json_decode( wp_remote_retrieve_body( $response ), true ) - ); + $body = wp_remote_retrieve_body( $response ); + + $items = json_decode( $body, true ); + + return $items ? $items : array(); + } + } + + /** + * Get list of connected Publicize connections. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_REST_Response suitable for 1-page collection + */ + public function get_items( $request ) { + $items = array(); + + foreach ( self::get_supported_services( $this->is_wpcom ) as $item ) { + $data = $this->prepare_item_for_response( $item, $request ); + + $items[] = $this->prepare_response_for_collection( $data ); } + + $response = rest_ensure_response( $items ); + $response->header( 'X-WP-Total', count( $items ) ); + $response->header( 'X-WP-TotalPages', 1 ); + + return $response; } /** From cb9409e4a0bbad1cb086bf1a4eacf093a67a45d9 Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Wed, 11 Dec 2024 10:23:00 +0530 Subject: [PATCH 5/8] Changelog --- .../publicize/changelog/add-social-unified-services-endpoint | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/publicize/changelog/add-social-unified-services-endpoint diff --git a/projects/packages/publicize/changelog/add-social-unified-services-endpoint b/projects/packages/publicize/changelog/add-social-unified-services-endpoint new file mode 100644 index 0000000000000..bfbd74c203cb0 --- /dev/null +++ b/projects/packages/publicize/changelog/add-social-unified-services-endpoint @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Added wpcom/v3/publicize/services endpoint From 88510a72b9beaaf2c131045bb61ceada06c5ad38 Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Wed, 11 Dec 2024 10:48:44 +0530 Subject: [PATCH 6/8] Phan needs a fan --- .../src/rest-endpoints/class-services-controller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/packages/publicize/src/rest-endpoints/class-services-controller.php b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php index 18e37df289cee..942607bf1ed35 100644 --- a/projects/packages/publicize/src/rest-endpoints/class-services-controller.php +++ b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php @@ -171,8 +171,8 @@ public function get_items( $request ) { } $response = rest_ensure_response( $items ); - $response->header( 'X-WP-Total', count( $items ) ); - $response->header( 'X-WP-TotalPages', 1 ); + $response->header( 'X-WP-Total', (string) count( $items ) ); + $response->header( 'X-WP-TotalPages', '1' ); return $response; } From 665ae2d2d775cbd219bee394cda0f7cf328050c4 Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Wed, 11 Dec 2024 12:44:29 +0530 Subject: [PATCH 7/8] Use the computed is_wpcom --- .../class-services-controller.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/projects/packages/publicize/src/rest-endpoints/class-services-controller.php b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php index 942607bf1ed35..dd5324989dfb9 100644 --- a/projects/packages/publicize/src/rest-endpoints/class-services-controller.php +++ b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php @@ -41,6 +41,15 @@ public function __construct() { $this->wpcom_is_wpcom_only_endpoint = true; } + /** + * Check if we are on WPCOM. + * + * @return bool + */ + protected static function is_wpcom() { + return defined( 'IS_WPCOM' ) && IS_WPCOM; + } + /** * Register the routes. */ @@ -111,12 +120,10 @@ public function get_item_schema() { /** * Get a list of Publicize supported services. * - * @param bool $is_wpcom Whether we are on WPCOM. - * * @return array */ - public static function get_supported_services( $is_wpcom = false ) { - if ( $is_wpcom ) { + public static function get_supported_services() { + if ( self::is_wpcom() ) { if ( function_exists( 'require_lib' ) ) { // @phan-suppress-next-line PhanUndeclaredFunction - phan is dumb not to see the function_exists check. require_lib( 'external-connections' ); @@ -164,7 +171,7 @@ public static function get_supported_services( $is_wpcom = false ) { public function get_items( $request ) { $items = array(); - foreach ( self::get_supported_services( $this->is_wpcom ) as $item ) { + foreach ( self::get_supported_services() as $item ) { $data = $this->prepare_item_for_response( $item, $request ); $items[] = $this->prepare_response_for_collection( $data ); From 4eb8da1946bfb875f300865c48170e5dd3883226 Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Wed, 11 Dec 2024 17:56:40 +0530 Subject: [PATCH 8/8] Extend the base class --- .../class-services-controller.php | 83 ++----------------- 1 file changed, 5 insertions(+), 78 deletions(-) diff --git a/projects/packages/publicize/src/rest-endpoints/class-services-controller.php b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php index dd5324989dfb9..5acfa7a6d9df1 100644 --- a/projects/packages/publicize/src/rest-endpoints/class-services-controller.php +++ b/projects/packages/publicize/src/rest-endpoints/class-services-controller.php @@ -9,45 +9,24 @@ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager; -use WP_Error; -use WP_REST_Controller; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; /** - * Registers the REST routes + * Services Controller class. */ -class Services_Controller extends WP_REST_Controller { - - /** - * Whether we are on WPCOM. - * - * @var bool $is_wpcom - */ - protected $is_wpcom = false; +class Services_Controller extends Base_Controller { /** * Constructor. */ public function __construct() { - $this->namespace = 'wpcom/v3'; + parent::__construct(); + $this->rest_base = 'publicize/services'; add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - - $this->is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM; - - $this->wpcom_is_wpcom_only_endpoint = true; - } - - /** - * Check if we are on WPCOM. - * - * @return bool - */ - protected static function is_wpcom() { - return defined( 'IS_WPCOM' ) && IS_WPCOM; } /** @@ -183,60 +162,8 @@ public function get_items( $request ) { return $response; } - - /** - * Filters out data based on ?_fields= request parameter - * - * @param array $item Item to prepare. - * @param WP_REST_Request $request Full details about the request. - * - * @return WP_REST_Response filtered item - */ - public function prepare_item_for_response( $item, $request ) { - if ( ! is_callable( array( $this, 'get_fields_for_response' ) ) ) { - return rest_ensure_response( $item ); - } - - $fields = $this->get_fields_for_response( $request ); - - $response_data = array(); - foreach ( $item as $field => $value ) { - if ( in_array( $field, $fields, true ) ) { - $response_data[ $field ] = $value; - } - } - - return rest_ensure_response( $response_data ); - } - - /** - * Verify that user can access Publicize data - * - * @return true|WP_Error - */ - public function get_items_permission_check() { - global $publicize; - - if ( ! $publicize ) { - return new WP_Error( - 'publicize_not_available', - __( 'Sorry, Jetpack Social is not available on your site right now.', 'jetpack-publicize-pkg' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - if ( $publicize->current_user_can_access_publicize_data() ) { - return true; - } - - return new WP_Error( - 'invalid_user_permission_publicize', - __( 'Sorry, you are not allowed to access Jetpack Social data on this site.', 'jetpack-publicize-pkg' ), - array( 'status' => rest_authorization_required_code() ) - ); - } } -if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { +if ( Base_Controller::is_wpcom() ) { wpcom_rest_api_v2_load_plugin( Services_Controller::class ); }