From e0f992b5a2e1a083df9ebc7d3a137206d6c8f9d8 Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Mon, 16 Dec 2024 23:34:36 +0000 Subject: [PATCH 01/11] Publicize: Mave the publicize-connections endpoint to the package This is following on from various discussions we've been having about how to refactor the API endpoints for connections management. This is a variation on the approach in #40607 but repurposes the existing endpoint rather than creating a new backwards compatible one. --- .../try-move-publicize-connections-endpoint | 4 + .../publicize/src/class-publicize-setup.php | 2 + ...2-endpoint-list-publicize-connections.php} | 152 ++++++++++++++---- ...t-api-v2-endpoint-publicize-share-post.php | 2 - .../publicize-connection-test-results.php | 4 +- .../try-move-publicize-connections-endpoint | 4 + 6 files changed, 133 insertions(+), 35 deletions(-) create mode 100644 projects/packages/publicize/changelog/try-move-publicize-connections-endpoint rename projects/{plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php => packages/publicize/src/class-wpcom-rest-api-v2-endpoint-list-publicize-connections.php} (55%) create mode 100644 projects/plugins/jetpack/changelog/try-move-publicize-connections-endpoint diff --git a/projects/packages/publicize/changelog/try-move-publicize-connections-endpoint b/projects/packages/publicize/changelog/try-move-publicize-connections-endpoint new file mode 100644 index 0000000000000..bc1a2d0bf680d --- /dev/null +++ b/projects/packages/publicize/changelog/try-move-publicize-connections-endpoint @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Publicize: Move the publicize-connections endpoint to the package diff --git a/projects/packages/publicize/src/class-publicize-setup.php b/projects/packages/publicize/src/class-publicize-setup.php index 383ef289fab5e..7e3e686b6fec0 100644 --- a/projects/packages/publicize/src/class-publicize-setup.php +++ b/projects/packages/publicize/src/class-publicize-setup.php @@ -37,6 +37,8 @@ public static function on_jetpack_feature_publicize_enabled() { } + new WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections(); + // Adding on a higher priority to make sure we're the first field registered. // The priority parameter can be removed once we deprecate WPCOM_REST_API_V2_Post_Publicize_Connections_Field add_action( 'rest_api_init', array( new Connections_Post_Field(), 'register_fields' ), 5 ); diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php b/projects/packages/publicize/src/class-wpcom-rest-api-v2-endpoint-list-publicize-connections.php similarity index 55% rename from projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php rename to projects/packages/publicize/src/class-wpcom-rest-api-v2-endpoint-list-publicize-connections.php index 67fa5b25ced9e..b0cd1aa6a7e77 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connections.php +++ b/projects/packages/publicize/src/class-wpcom-rest-api-v2-endpoint-list-publicize-connections.php @@ -4,6 +4,15 @@ * * @package automattic/jetpack */ +namespace Automattic\Jetpack\Publicize; + +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; /** * Publicize: List Connections @@ -65,39 +74,85 @@ public function register_routes() { * @return array */ protected function get_connection_schema_properties() { - return array( + $deprecated_fields = array( 'id' => array( - 'description' => __( 'Unique identifier for the Jetpack Social connection', 'jetpack' ), - 'type' => 'string', - ), - 'service_name' => array( - 'description' => __( 'Alphanumeric identifier for the Jetpack Social service', 'jetpack' ), - 'type' => 'string', - ), - 'display_name' => array( - 'description' => __( 'Display name of the connected account', 'jetpack' ), 'type' => 'string', + 'description' => __( 'Unique identifier for the Jetpack Social connection.', 'jetpack-publicize-pkg' ) . ' ' . sprintf( + /* translators: %s is the new field name */ + __( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ), + 'connection_id' + ), ), 'username' => array( - 'description' => __( 'Username of the connected account', 'jetpack' ), 'type' => 'string', + 'description' => __( 'Username of the connected account.', 'jetpack-publicize-pkg' ) . ' ' . sprintf( + /* translators: %s is the new field name */ + __( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ), + 'external_handle' + ), ), 'profile_display_name' => array( - 'description' => __( 'The name to display in the profile of the connected account', 'jetpack' ), - 'type' => 'string', - ), - 'profile_picture' => array( - 'description' => __( 'Profile picture of the connected account', 'jetpack' ), 'type' => 'string', + 'description' => __( 'The name to display in the profile of the connected account.', 'jetpack-publicize-pkg' ) . ' ' . sprintf( + /* translators: %s is the new field name */ + __( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ), + 'display_name' + ), ), 'global' => array( - 'description' => __( 'Is this connection available to all users?', 'jetpack' ), 'type' => 'boolean', + 'description' => __( 'Is this connection available to all users?', 'jetpack-publicize-pkg' ) . ' ' . sprintf( + /* translators: %s is the new field name */ + __( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ), + 'shared' + ), ), - 'external_id' => array( - 'description' => __( 'The external ID of the connected account', 'jetpack' ), - 'type' => 'string', - ), + ); + + return array_merge( + $deprecated_fields, + array( + 'connection_id' => array( + 'type' => 'string', + 'description' => __( 'Connection ID of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'display_name' => array( + 'type' => 'string', + 'description' => __( 'Display name of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'external_handle' => array( + 'type' => 'string', + 'description' => __( 'The external handle or username of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'external_id' => array( + 'type' => 'string', + 'description' => __( 'The external ID of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'profile_link' => array( + 'type' => 'string', + 'description' => __( 'Profile link of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'profile_picture' => array( + 'type' => 'string', + 'description' => __( 'URL of the profile picture of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'service_label' => array( + 'type' => 'string', + 'description' => __( 'Human-readable label for the Jetpack Social service.', 'jetpack-publicize-pkg' ), + ), + 'service_name' => array( + 'type' => 'string', + 'description' => __( 'Alphanumeric identifier for the Jetpack Social service.', 'jetpack-publicize-pkg' ), + ), + 'shared' => array( + 'type' => 'boolean', + 'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ), + ), + 'user_id' => array( + 'type' => 'integer', + 'description' => __( 'ID of the user the connection belongs to.', 'jetpack-publicize-pkg' ), + ), + ) ); } @@ -133,18 +188,26 @@ protected function get_connections() { foreach ( $connections as $connection ) { $connection_meta = $publicize->get_connection_meta( $connection ); $connection_data = $connection_meta['connection_data']; + $connection_id = $publicize->get_connection_id( $connection ); $items[] = array( - 'id' => (string) $publicize->get_connection_unique_id( $connection ), - 'connection_id' => (string) $publicize->get_connection_id( $connection ), - 'service_name' => $service_name, + 'connection_id' => $connection_id, 'display_name' => $publicize->get_display_name( $service_name, $connection ), + 'external_handle' => $publicize->get_external_handle( $service_name, $connection ), + 'external_id' => $connection_meta['external_id'] ?? '', + 'profile_link' => $publicize->get_profile_link( $service_name, $connection ), + 'profile_picture' => $publicize->get_profile_picture( $connection ), + 'service_label' => Publicize::get_service_label( $service_name ), + 'service_name' => $service_name, + 'shared' => ! $connection_data['user_id'], + 'user_id' => (int) $connection_data['user_id'], + + // Deprecated fields. + 'id' => (string) $publicize->get_connection_unique_id( $connection ), 'username' => $publicize->get_username( $service_name, $connection ), 'profile_display_name' => ! empty( $connection_meta['profile_display_name'] ) ? $connection_meta['profile_display_name'] : '', - 'profile_picture' => ! empty( $connection_meta['profile_picture'] ) ? $connection_meta['profile_picture'] : '', // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- We expect an integer, but do loose comparison below in case some other type is stored. 'global' => 0 == $connection_data['user_id'], - 'external_id' => $connection_meta['external_id'] ?? '', ); } } @@ -162,7 +225,9 @@ protected function get_connections() { public function get_items( $request ) { $items = array(); - foreach ( $this->get_connections() as $item ) { + $connections = ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ? $this->get_connections() : $this->get_connections_from_wpcom(); + + foreach ( $connections as $item ) { $items[] = $this->prepare_item_for_response( $item, $request ); } @@ -173,6 +238,30 @@ public function get_items( $request ) { return $response; } + /** + * Proxy the request to the WPCOM API + * + * @return array Connection objects in the same shape as from `get_connections` + */ + protected function get_connections_from_wpcom() { + $site_id = Manager::get_site_id( true ); + if ( ! $site_id ) { + return array(); + } + + $response = Client::wpcom_json_api_request_as_user( sprintf( '/sites/%d/publicize/connections', $site_id ), 'v2', array( 'method' => 'GET' ) ); + + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + // TODO log error. + return array(); + } + + $body = wp_remote_retrieve_body( $response ); + + $items = json_decode( $body, true ); + return $items ? $items : array(); + } + /** * Filters out data based on ?_fields= request parameter * @@ -209,7 +298,7 @@ public function get_items_permission_check() { if ( ! $publicize ) { return new WP_Error( 'publicize_not_available', - __( 'Sorry, Jetpack Social is not available on your site right now.', 'jetpack' ), + __( 'Sorry, Jetpack Social is not available on your site right now.', 'jetpack-publicize-pkg' ), array( 'status' => rest_authorization_required_code() ) ); } @@ -220,9 +309,12 @@ public function get_items_permission_check() { return new WP_Error( 'invalid_user_permission_publicize', - __( 'Sorry, you are not allowed to access Jetpack Social data on this site.', 'jetpack' ), + __( 'Sorry, you are not allowed to access Jetpack Social data on this site.', 'jetpack-publicize-pkg' ), array( 'status' => rest_authorization_required_code() ) ); } } -wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections' ); + +if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + wpcom_rest_api_v2_load_plugin( 'Automattic\Jetpack\Publicize\WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections' ); +} diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-publicize-share-post.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-publicize-share-post.php index f9522d94231e6..764b00f4a85d2 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-publicize-share-post.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-publicize-share-post.php @@ -10,8 +10,6 @@ use Automattic\Jetpack\Connection\Client; -require_once __DIR__ . '/publicize-connections.php'; - /** * Publicize: Share post class. */ diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php index 38b8529d53050..012bee756d672 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php @@ -5,8 +5,6 @@ * @package automattic/jetpack */ -require_once __DIR__ . '/publicize-connections.php'; - /** * Publicize: List Connection Test Result Data * @@ -14,7 +12,7 @@ * * @since 6.8 */ -class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results extends WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections { +class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results extends Automattic\Jetpack\Publicize\WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections { /** * Constructor. */ diff --git a/projects/plugins/jetpack/changelog/try-move-publicize-connections-endpoint b/projects/plugins/jetpack/changelog/try-move-publicize-connections-endpoint new file mode 100644 index 0000000000000..ea450e15a2a54 --- /dev/null +++ b/projects/plugins/jetpack/changelog/try-move-publicize-connections-endpoint @@ -0,0 +1,4 @@ +Significance: minor +Type: other + +Publicize: Move the publicize-connections endpoint to the package From 2d94338151f8ac1d3cc7372ea14428a833ae8a23 Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 10:47:11 +0000 Subject: [PATCH 02/11] Rename the endpoint class --- projects/packages/publicize/src/class-publicize-setup.php | 2 +- .../class-connections-controller.php} | 4 ++-- .../wpcom-endpoints/publicize-connection-test-results.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename projects/packages/publicize/src/{class-wpcom-rest-api-v2-endpoint-list-publicize-connections.php => rest-api/class-connections-controller.php} (98%) diff --git a/projects/packages/publicize/src/class-publicize-setup.php b/projects/packages/publicize/src/class-publicize-setup.php index 7e3e686b6fec0..ccbc29528a834 100644 --- a/projects/packages/publicize/src/class-publicize-setup.php +++ b/projects/packages/publicize/src/class-publicize-setup.php @@ -37,7 +37,7 @@ public static function on_jetpack_feature_publicize_enabled() { } - new WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections(); + new REST_API\Connections_Controller(); // Adding on a higher priority to make sure we're the first field registered. // The priority parameter can be removed once we deprecate WPCOM_REST_API_V2_Post_Publicize_Connections_Field diff --git a/projects/packages/publicize/src/class-wpcom-rest-api-v2-endpoint-list-publicize-connections.php b/projects/packages/publicize/src/rest-api/class-connections-controller.php similarity index 98% rename from projects/packages/publicize/src/class-wpcom-rest-api-v2-endpoint-list-publicize-connections.php rename to projects/packages/publicize/src/rest-api/class-connections-controller.php index b0cd1aa6a7e77..cbef3a041a59e 100644 --- a/projects/packages/publicize/src/class-wpcom-rest-api-v2-endpoint-list-publicize-connections.php +++ b/projects/packages/publicize/src/rest-api/class-connections-controller.php @@ -4,7 +4,7 @@ * * @package automattic/jetpack */ -namespace Automattic\Jetpack\Publicize; +namespace Automattic\Jetpack\Publicize\REST_API; use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager; @@ -29,7 +29,7 @@ * * @since 6.8 */ -class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections extends WP_REST_Controller { +class Connections_Controller extends WP_REST_Controller { /** * Flag to help WordPress.com decide where it should look for * Publicize data. Ignored for direct requests to Jetpack sites. diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php index 012bee756d672..907752889396e 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php @@ -12,7 +12,7 @@ * * @since 6.8 */ -class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results extends Automattic\Jetpack\Publicize\WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections { +class WPCOM_REST_API_V2_Endpoint_List_Publicize_Connection_Test_Results extends Automattic\Jetpack\Publicize\REST_API\Connections_Controller { /** * Constructor. */ From eb7e188cd487e748f653abe5d49ad814aba17aea Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 13:08:28 +0000 Subject: [PATCH 03/11] Make the get_connections methods static --- .../rest-api/class-connections-controller.php | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/projects/packages/publicize/src/rest-api/class-connections-controller.php b/projects/packages/publicize/src/rest-api/class-connections-controller.php index cbef3a041a59e..e1d93bbd4b071 100644 --- a/projects/packages/publicize/src/rest-api/class-connections-controller.php +++ b/projects/packages/publicize/src/rest-api/class-connections-controller.php @@ -179,7 +179,7 @@ public function get_item_schema() { * @internal * @return array */ - protected function get_connections() { + protected static function get_connections() { global $publicize; $items = array(); @@ -216,26 +216,13 @@ protected function get_connections() { } /** - * Get list of connected Publicize connections. - * - * @param WP_REST_Request $request Full details about the request. + * Get the connections data, either directly or by calling the WPCOM API * - * @return WP_REST_Response suitable for 1-page collection + * @return array Connection objects in the same shape as from `get_connections` */ - public function get_items( $request ) { - $items = array(); - - $connections = ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ? $this->get_connections() : $this->get_connections_from_wpcom(); - - foreach ( $connections as $item ) { - $items[] = $this->prepare_item_for_response( $item, $request ); - } - - $response = rest_ensure_response( $items ); - $response->header( 'X-WP-Total', count( $items ) ); - $response->header( 'X-WP-TotalPages', 1 ); - - return $response; + public static function get_connections_data() { + // TODO decide on caching + return ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ? self::get_connections() : self::get_connections_from_wpcom(); } /** @@ -243,7 +230,7 @@ public function get_items( $request ) { * * @return array Connection objects in the same shape as from `get_connections` */ - protected function get_connections_from_wpcom() { + protected static function get_connections_from_wpcom() { $site_id = Manager::get_site_id( true ); if ( ! $site_id ) { return array(); @@ -262,6 +249,29 @@ protected function get_connections_from_wpcom() { 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(); + + $connections = self::get_connections_data(); + + foreach ( $connections as $item ) { + $items[] = $this->prepare_item_for_response( $item, $request ); + } + + $response = rest_ensure_response( $items ); + $response->header( 'X-WP-Total', count( $items ) ); + $response->header( 'X-WP-TotalPages', 1 ); + + return $response; + } + /** * Filters out data based on ?_fields= request parameter * From 24d27c13ecd79d4892acd5a801049577435be831 Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 18:56:00 +0000 Subject: [PATCH 04/11] Only use the connection meta in order to fix a bug. --- .../publicize/src/class-publicize-base.php | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/projects/packages/publicize/src/class-publicize-base.php b/projects/packages/publicize/src/class-publicize-base.php index b88bd1a6b12ca..48b96da65436b 100644 --- a/projects/packages/publicize/src/class-publicize-base.php +++ b/projects/packages/publicize/src/class-publicize-base.php @@ -497,8 +497,8 @@ public function get_profile_link( $service_name, $connection ) { return 'https://instagram.com/' . $cmeta['connection_data']['meta']['username']; } - if ( 'threads' === $service_name && isset( $connection['external_name'] ) ) { - return 'https://www.threads.net/@' . $connection['external_name']; + if ( 'threads' === $service_name && isset( $cmeta['external_name'] ) ) { + return 'https://www.threads.net/@' . $cmeta['external_name']; } if ( 'mastodon' === $service_name && isset( $cmeta['external_name'] ) ) { @@ -527,7 +527,7 @@ public function get_profile_link( $service_name, $connection ) { } $profile_url_query = wp_parse_url( $cmeta['connection_data']['meta']['profile_url'], PHP_URL_QUERY ); - $profile_url_query_args = null; + $profile_url_query_args = array(); wp_parse_str( $profile_url_query, $profile_url_query_args ); $id = null; @@ -589,17 +589,25 @@ public function get_display_name( $service_name, $connection ) { * @return string */ public function get_username( $service_name, $connection ) { - $cmeta = $this->get_connection_meta( $connection ); + $cmeta = $this->get_connection_meta( $connection ); + $username = ''; - if ( 'mastodon' === $service_name && isset( $cmeta['external_display'] ) ) { - return $cmeta['external_display']; + switch ( $service_name ) { + case 'mastodon': + $username = $cmeta['external_display'] ?? ''; + break; + case 'bluesky': + case 'threads': + $username = $cmeta['external_name'] ?? ''; + break; } - if ( isset( $cmeta['connection_data']['meta']['username'] ) ) { - return $cmeta['connection_data']['meta']['username']; + if ( empty( $username ) ) { + $username = $cmeta['connection-data']['meta']['username'] ?? ''; + $username = empty( $username ) ? $this->get_display_name( $service_name, $connection ) : $username; } - return $this->get_display_name( $service_name, $connection ); + return $username; } /** @@ -608,7 +616,7 @@ public function get_username( $service_name, $connection ) { * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack). * @return string */ - private function get_profile_picture( $connection ) { + public function get_profile_picture( $connection ) { $cmeta = $this->get_connection_meta( $connection ); if ( isset( $cmeta['profile_picture'] ) ) { From 78afbc0f760eeb465c2be830da94716a279f6cbb Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 19:03:08 +0000 Subject: [PATCH 05/11] Correct class name --- .../publicize/src/rest-api/class-connections-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/packages/publicize/src/rest-api/class-connections-controller.php b/projects/packages/publicize/src/rest-api/class-connections-controller.php index e1d93bbd4b071..abd82518e9ae0 100644 --- a/projects/packages/publicize/src/rest-api/class-connections-controller.php +++ b/projects/packages/publicize/src/rest-api/class-connections-controller.php @@ -326,5 +326,5 @@ public function get_items_permission_check() { } if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { - wpcom_rest_api_v2_load_plugin( 'Automattic\Jetpack\Publicize\WPCOM_REST_API_V2_Endpoint_List_Publicize_Connections' ); + wpcom_rest_api_v2_load_plugin( 'Automattic\Jetpack\Publicize\REST_API\Connections_Controller' ); } From 64e2cfd6e2bcfd39d68ae2174306a32640c4f6f0 Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 19:04:27 +0000 Subject: [PATCH 06/11] Add test connections functionality --- .../rest-api/class-connections-controller.php | 237 +++++++++++++----- .../publicize-connection-test-results.php | 28 +-- 2 files changed, 170 insertions(+), 95 deletions(-) diff --git a/projects/packages/publicize/src/rest-api/class-connections-controller.php b/projects/packages/publicize/src/rest-api/class-connections-controller.php index abd82518e9ae0..fcd208b5472c9 100644 --- a/projects/packages/publicize/src/rest-api/class-connections-controller.php +++ b/projects/packages/publicize/src/rest-api/class-connections-controller.php @@ -8,6 +8,7 @@ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager; +use Automattic\Jetpack\Publicize\Publicize; use WP_Error; use WP_REST_Controller; use WP_REST_Request; @@ -60,6 +61,12 @@ public function register_routes() { 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permission_check' ), + 'args' => array( + 'test_connections' => array( + 'type' => 'boolean', + 'description' => __( 'Whether to test connections.', 'jetpack-publicize-pkg' ), + ), + ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) @@ -109,50 +116,89 @@ protected function get_connection_schema_properties() { ), ); + $connection_fields = array( + 'connection_id' => array( + 'type' => 'string', + 'description' => __( 'Connection ID of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'display_name' => array( + 'type' => 'string', + 'description' => __( 'Display name of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'external_handle' => array( + 'type' => 'string', + 'description' => __( 'The external handle or username of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'external_id' => array( + 'type' => 'string', + 'description' => __( 'The external ID of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'profile_link' => array( + 'type' => 'string', + 'description' => __( 'Profile link of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'profile_picture' => array( + 'type' => 'string', + 'description' => __( 'URL of the profile picture of the connected account.', 'jetpack-publicize-pkg' ), + ), + 'service_label' => array( + 'type' => 'string', + 'description' => __( 'Human-readable label for the Jetpack Social service.', 'jetpack-publicize-pkg' ), + ), + 'service_name' => array( + 'type' => 'string', + 'description' => __( 'Alphanumeric identifier for the Jetpack Social service.', 'jetpack-publicize-pkg' ), + ), + 'shared' => array( + 'type' => 'boolean', + 'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ), + ), + 'user_id' => array( + 'type' => 'integer', + 'description' => __( 'ID of the user the connection belongs to.', 'jetpack-publicize-pkg' ), + ), + ); + + $test_fields = array( + 'status' => array( + 'type' => 'string', + 'description' => __( 'The connection status.', 'jetpack-publicize-pkg' ), + 'enum' => array( + 'ok', + 'broken', + ), + ), + 'test_success' => array( + 'description' => __( 'Did the Jetpack Social connection test pass?', 'jetpack-publicize-pkg' ), + 'type' => 'boolean', + ), + 'error_code' => array( + 'description' => __( 'Jetpack Social connection error code', 'jetpack-publicize-pkg' ), + 'type' => 'string', + ), + 'test_message' => array( + 'description' => __( 'Jetpack Social connection success or error message', 'jetpack-publicize-pkg' ), + 'type' => 'string', + ), + 'can_refresh' => array( + 'description' => __( 'Can the current user refresh the Jetpack Social connection?', 'jetpack-publicize-pkg' ), + 'type' => 'boolean', + ), + 'refresh_text' => array( + 'description' => __( 'Message instructing the user to refresh their Connection to the Jetpack Social service', 'jetpack-publicize-pkg' ), + 'type' => 'string', + ), + 'refresh_url' => array( + 'description' => __( 'URL for refreshing the Connection to the Jetpack Social service', 'jetpack-publicize-pkg' ), + 'type' => 'string', + 'format' => 'uri', + ), + ); + return array_merge( $deprecated_fields, - array( - 'connection_id' => array( - 'type' => 'string', - 'description' => __( 'Connection ID of the connected account.', 'jetpack-publicize-pkg' ), - ), - 'display_name' => array( - 'type' => 'string', - 'description' => __( 'Display name of the connected account.', 'jetpack-publicize-pkg' ), - ), - 'external_handle' => array( - 'type' => 'string', - 'description' => __( 'The external handle or username of the connected account.', 'jetpack-publicize-pkg' ), - ), - 'external_id' => array( - 'type' => 'string', - 'description' => __( 'The external ID of the connected account.', 'jetpack-publicize-pkg' ), - ), - 'profile_link' => array( - 'type' => 'string', - 'description' => __( 'Profile link of the connected account.', 'jetpack-publicize-pkg' ), - ), - 'profile_picture' => array( - 'type' => 'string', - 'description' => __( 'URL of the profile picture of the connected account.', 'jetpack-publicize-pkg' ), - ), - 'service_label' => array( - 'type' => 'string', - 'description' => __( 'Human-readable label for the Jetpack Social service.', 'jetpack-publicize-pkg' ), - ), - 'service_name' => array( - 'type' => 'string', - 'description' => __( 'Alphanumeric identifier for the Jetpack Social service.', 'jetpack-publicize-pkg' ), - ), - 'shared' => array( - 'type' => 'boolean', - 'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ), - ), - 'user_id' => array( - 'type' => 'integer', - 'description' => __( 'ID of the user the connection belongs to.', 'jetpack-publicize-pkg' ), - ), - ) + $connection_fields, + $test_fields ); } @@ -177,66 +223,121 @@ public function get_item_schema() { * the Connection Test Result endpoint. * * @internal + * @param bool $run_tests Whether to run the tests to check the health of the connections. * @return array */ - protected static function get_connections() { + protected static function get_connections( $run_tests = false ) { global $publicize; - $items = array(); + $items = array(); + $test_results = $run_tests ? self::get_connections_test_status() : array(); foreach ( (array) $publicize->get_services( 'connected' ) as $service_name => $connections ) { foreach ( $connections as $connection ) { $connection_meta = $publicize->get_connection_meta( $connection ); $connection_data = $connection_meta['connection_data']; $connection_id = $publicize->get_connection_id( $connection ); - - $items[] = array( - 'connection_id' => $connection_id, - 'display_name' => $publicize->get_display_name( $service_name, $connection ), - 'external_handle' => $publicize->get_external_handle( $service_name, $connection ), - 'external_id' => $connection_meta['external_id'] ?? '', - 'profile_link' => $publicize->get_profile_link( $service_name, $connection ), - 'profile_picture' => $publicize->get_profile_picture( $connection ), - 'service_label' => Publicize::get_service_label( $service_name ), - 'service_name' => $service_name, - 'shared' => ! $connection_data['user_id'], - 'user_id' => (int) $connection_data['user_id'], - - // Deprecated fields. - 'id' => (string) $publicize->get_connection_unique_id( $connection ), - 'username' => $publicize->get_username( $service_name, $connection ), - 'profile_display_name' => ! empty( $connection_meta['profile_display_name'] ) ? $connection_meta['profile_display_name'] : '', - // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- We expect an integer, but do loose comparison below in case some other type is stored. - 'global' => 0 == $connection_data['user_id'], + $username = $publicize->get_username( $service_name, $connection ); + $item = array_merge( + array( + 'connection_id' => $connection_id, + 'display_name' => $publicize->get_display_name( $service_name, $connection ), + 'external_handle' => $username, + 'external_id' => $connection_meta['external_id'] ?? '', + 'profile_link' => $publicize->get_profile_link( $service_name, $connection ), + 'profile_picture' => $publicize->get_profile_picture( $connection ), + 'service_label' => Publicize::get_service_label( $service_name ), + 'service_name' => $service_name, + 'shared' => ! $connection_data['user_id'], + 'user_id' => (int) $connection_data['user_id'], + + // Deprecated fields. + 'id' => (string) $publicize->get_connection_unique_id( $connection ), + 'username' => $username, + 'profile_display_name' => ! empty( $connection_meta['profile_display_name'] ) ? $connection_meta['profile_display_name'] : '', + // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- We expect an integer, but do loose comparison below in case some other type is stored. + 'global' => 0 == $connection_data['user_id'], + ), + $test_results[ $connection_id ] ?? array() ); + $items[] = $item; } } return $items; } + /** + * Get the connections test status. + * + * @return array + */ + protected static function get_connections_test_status() { + /** + * Publicize instance. + * + * @var \Automattic\Jetpack\Publicize\Publicize $publicize + */ + global $publicize; + + $test_results = $publicize->get_publicize_conns_test_results(); + + $test_results_map = array(); + $mapping = array( + 'test_success' => 'connectionTestPassed', + 'test_message' => 'connectionTestMessage', + 'error_code' => 'connectionTestErrorCode', + 'can_refresh' => 'userCanRefresh', + 'refresh_text' => 'refreshText', + 'refresh_url' => 'refreshURL', + 'connection_id' => 'connectionID', + ); + + foreach ( $test_results as $test_result ) { + $result_fields = array( + // Compare to `true` because the API returns a 'must_reauth' for LinkedIn. + 'status' => true === $test_result['connectionTestPassed'] ? 'ok' : 'broken', + ); + foreach ( $mapping as $field => $test_result_field ) { + $result_fields[ $field ] = $test_result[ $test_result_field ]; + } + $test_results_map[ $test_result['connectionID'] ] = $result_fields; + } + + return $test_results_map; + } + /** * Get the connections data, either directly or by calling the WPCOM API * + * @param bool $run_tests Whether to run the tests to check the health of the connections. + * * @return array Connection objects in the same shape as from `get_connections` */ - public static function get_connections_data() { + public static function get_connections_data( $run_tests = false ) { // TODO decide on caching - return ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ? self::get_connections() : self::get_connections_from_wpcom(); + return ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ? self::get_connections( $run_tests ) : self::get_connections_from_wpcom( $run_tests ); } /** * Proxy the request to the WPCOM API * + * @param bool $run_tests Whether to run the tests to check the health of the connections. + * * @return array Connection objects in the same shape as from `get_connections` */ - protected static function get_connections_from_wpcom() { + protected static function get_connections_from_wpcom( $run_tests = false ) { $site_id = Manager::get_site_id( true ); if ( ! $site_id ) { return array(); } - $response = Client::wpcom_json_api_request_as_user( sprintf( '/sites/%d/publicize/connections', $site_id ), 'v2', array( 'method' => 'GET' ) ); + $path = add_query_arg( + array( 'test_connections' => $run_tests ), + sprintf( '/sites/%d/publicize/connections', $site_id ) + ); + + $response = Client::wpcom_json_api_request_as_user( sprintf( $path, $site_id ), 'v2', array( 'method' => 'GET' ) ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { // TODO log error. @@ -259,7 +360,7 @@ protected static function get_connections_from_wpcom() { public function get_items( $request ) { $items = array(); - $connections = self::get_connections_data(); + $connections = self::get_connections_data( $request->get_param( 'test_connections' ) ); foreach ( $connections as $item ) { $items[] = $this->prepare_item_for_response( $item, $request ); diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php index 907752889396e..8e6b1d75675ac 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php @@ -51,33 +51,7 @@ public function get_item_schema() { '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'jetpack-publicize-connection-test-results', 'type' => 'object', - 'properties' => $this->get_connection_schema_properties() + array( - 'test_success' => array( - 'description' => __( 'Did the Jetpack Social connection test pass?', 'jetpack' ), - 'type' => 'boolean', - ), - 'error_code' => array( - 'description' => __( 'Jetpack Social connection error code', 'jetpack' ), - 'type' => 'string', - ), - 'test_message' => array( - 'description' => __( 'Jetpack Social connection success or error message', 'jetpack' ), - 'type' => 'string', - ), - 'can_refresh' => array( - 'description' => __( 'Can the current user refresh the Jetpack Social connection?', 'jetpack' ), - 'type' => 'boolean', - ), - 'refresh_text' => array( - 'description' => __( 'Message instructing the user to refresh their Connection to the Jetpack Social service', 'jetpack' ), - 'type' => 'string', - ), - 'refresh_url' => array( - 'description' => __( 'URL for refreshing the Connection to the Jetpack Social service', 'jetpack' ), - 'type' => 'string', - 'format' => 'uri', - ), - ), + 'properties' => $this->get_connection_schema_properties(), ); return $this->add_additional_fields_schema( $schema ); From 684d5c7d4322b1f8db9256c67477d23aee29864d Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 19:10:13 +0000 Subject: [PATCH 07/11] Adapt publicize-connection-test-results to use the updated code --- .../publicize-connection-test-results.php | 37 +------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php index 8e6b1d75675ac..6213da0255bb6 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php @@ -62,45 +62,10 @@ public function get_item_schema() { * * @param WP_REST_Request $request Full details about the request. * - * @see Publicize::get_publicize_conns_test_results() * @return WP_REST_Response suitable for 1-page collection */ public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - global $publicize; - - $items = $this->get_connections(); - - $test_results = $publicize->get_publicize_conns_test_results(); - $test_results_by_unique_id = array(); - foreach ( $test_results as $test_result ) { - $test_results_by_unique_id[ $test_result['connectionID'] ] = $test_result; - } - - $mapping = array( - 'test_success' => 'connectionTestPassed', - 'test_message' => 'connectionTestMessage', - 'error_code' => 'connectionTestErrorCode', - 'can_refresh' => 'userCanRefresh', - 'refresh_text' => 'refreshText', - 'refresh_url' => 'refreshURL', - 'connection_id' => 'connectionID', - ); - - foreach ( $items as &$item ) { - $test_result = $test_results_by_unique_id[ $item['connection_id'] ]; - - foreach ( $mapping as $field => $test_result_field ) { - $item[ $field ] = $test_result[ $test_result_field ]; - } - } - - if ( - isset( $item['id'] ) - && 'linkedin' === $item['id'] - && 'must_reauth' === $test_result['connectionTestPassed'] - ) { - $item['test_success'] = 'must_reauth'; - } + $items = $this->get_connections( true ); $response = rest_ensure_response( $items ); From e75b93c9a567d3116ca8e789fe826e6bc86f1fb6 Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 19:22:17 +0000 Subject: [PATCH 08/11] Call get_connections as a static method --- .../wpcom-endpoints/publicize-connection-test-results.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php index 6213da0255bb6..52b769ad704ad 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php @@ -65,7 +65,7 @@ public function get_item_schema() { * @return WP_REST_Response suitable for 1-page collection */ public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - $items = $this->get_connections( true ); + $items = self::get_connections( true ); $response = rest_ensure_response( $items ); From 47c2044ffcbf6328220c25ea7a45c34d564e2a82 Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 20:28:22 +0000 Subject: [PATCH 09/11] Ignore Phan error --- projects/packages/publicize/src/class-publicize-setup.php | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/packages/publicize/src/class-publicize-setup.php b/projects/packages/publicize/src/class-publicize-setup.php index ccbc29528a834..e7ce11c4779c0 100644 --- a/projects/packages/publicize/src/class-publicize-setup.php +++ b/projects/packages/publicize/src/class-publicize-setup.php @@ -37,6 +37,7 @@ public static function on_jetpack_feature_publicize_enabled() { } + // @phan-suppress-next-line PhanNoopNew new REST_API\Connections_Controller(); // Adding on a higher priority to make sure we're the first field registered. From f1d36ef789ddb73e06d8b9a89ef44b41cd9f9496 Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 20:28:49 +0000 Subject: [PATCH 10/11] Implement schema caching --- .../publicize/src/rest-api/class-connections-controller.php | 6 ++++++ .../wpcom-endpoints/publicize-connection-test-results.php | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/projects/packages/publicize/src/rest-api/class-connections-controller.php b/projects/packages/publicize/src/rest-api/class-connections-controller.php index fcd208b5472c9..e69636dd432ce 100644 --- a/projects/packages/publicize/src/rest-api/class-connections-controller.php +++ b/projects/packages/publicize/src/rest-api/class-connections-controller.php @@ -208,6 +208,10 @@ protected function get_connection_schema_properties() { * @return array */ public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'jetpack-publicize-connection', @@ -215,6 +219,8 @@ public function get_item_schema() { 'properties' => $this->get_connection_schema_properties(), ); + $this->schema = $schema; + return $this->add_additional_fields_schema( $schema ); } diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php index 52b769ad704ad..aab9032634b15 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php @@ -47,6 +47,10 @@ public function register_routes() { * @return array */ public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'jetpack-publicize-connection-test-results', @@ -54,6 +58,8 @@ public function get_item_schema() { 'properties' => $this->get_connection_schema_properties(), ); + $this->schema = $schema; + return $this->add_additional_fields_schema( $schema ); } From cf9e957cd646f22a2db47c6282a16b5a98c8f22e Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Tue, 17 Dec 2024 22:57:04 +0000 Subject: [PATCH 11/11] Fix Phan errors --- .../src/rest-api/class-connections-controller.php | 15 ++++++--------- .../publicize-connection-test-results.php | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/projects/packages/publicize/src/rest-api/class-connections-controller.php b/projects/packages/publicize/src/rest-api/class-connections-controller.php index e69636dd432ce..dbded4dcf7aee 100644 --- a/projects/packages/publicize/src/rest-api/class-connections-controller.php +++ b/projects/packages/publicize/src/rest-api/class-connections-controller.php @@ -58,7 +58,7 @@ public function register_routes() { '/' . $this->rest_base, array( array( - 'methods' => WP_REST_Server::READABLE, + 'methods' => WP_REST_Server::READABLE, // @phan-suppress-current-line PhanPluginMixedKeyNoKey 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permission_check' ), 'args' => array( @@ -373,8 +373,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; } @@ -388,20 +388,16 @@ public function get_items( $request ) { * @return array filtered $connection */ public function prepare_item_for_response( $connection, $request ) { - if ( ! is_callable( array( $this, 'get_fields_for_response' ) ) ) { - return $connection; - } - $fields = $this->get_fields_for_response( $request ); $response_data = array(); foreach ( $connection as $field => $value ) { - if ( in_array( $field, $fields, true ) ) { + if ( rest_is_field_included( $field, $fields ) ) { $response_data[ $field ] = $value; } } - return $response_data; + return rest_ensure_response( $response_data ); } /** @@ -433,5 +429,6 @@ public function get_items_permission_check() { } if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + // @phan-suppress-next-line PhanUndeclaredFunction wpcom_rest_api_v2_load_plugin( 'Automattic\Jetpack\Publicize\REST_API\Connections_Controller' ); } diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php index aab9032634b15..dc1a6ca70719b 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-connection-test-results.php @@ -32,7 +32,7 @@ public function register_routes() { '/' . $this->rest_base, array( array( - 'methods' => WP_REST_Server::READABLE, + 'methods' => WP_REST_Server::READABLE, // @phan-suppress-current-line PhanPluginMixedKeyNoKey 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permission_check' ), ),