Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
19.13% covered (danger)
19.13%
53 / 277
8.33% covered (danger)
8.33%
1 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Connections_Controller
18.91% covered (danger)
18.91%
52 / 275
8.33% covered (danger)
8.33%
1 / 12
578.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 register_routes
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 1
2
 get_item_schema
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
6
 get_the_item_schema
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
1 / 1
1
 get_items_permissions_check
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_items
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 create_item_permissions_check
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 create_item
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
30
 update_item_permissions_check
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 update_item
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
20
 delete_item_permissions_check
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 delete_item
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * The Publicize Connections Controller class.
4 *
5 * @package automattic/jetpack-publicize
6 */
7
8namespace Automattic\Jetpack\Publicize\REST_API;
9
10use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
11use Automattic\Jetpack\Publicize\Connections;
12use Automattic\Jetpack\Publicize\Publicize_Utils;
13use WP_Error;
14use WP_REST_Request;
15use WP_REST_Response;
16use WP_REST_Server;
17
18if ( ! defined( 'ABSPATH' ) ) {
19    exit( 0 );
20}
21
22/**
23 * Connections Controller class.
24 *
25 * @phan-constructor-used-for-side-effects
26 */
27class Connections_Controller extends Base_Controller {
28
29    use WPCOM_REST_API_Proxy_Request;
30
31    /**
32     * Constructor.
33     */
34    public function __construct() {
35        parent::__construct();
36
37        $this->base_api_path = 'wpcom';
38        $this->version       = 'v2';
39
40        $this->namespace = "{$this->base_api_path}/{$this->version}";
41        $this->rest_base = 'publicize/connections';
42
43        $this->allow_requests_as_blog = true;
44
45        add_action( 'rest_api_init', array( $this, 'register_routes' ) );
46    }
47
48    /**
49     * Register the routes.
50     */
51    public function register_routes() {
52        register_rest_route(
53            $this->namespace,
54            '/' . $this->rest_base,
55            array(
56                array(
57                    'methods'             => WP_REST_Server::READABLE,
58                    'callback'            => array( $this, 'get_items' ),
59                    'permission_callback' => array( $this, 'get_items_permissions_check' ),
60                    'args'                => array(
61                        'test_connections' => array(
62                            'type'        => 'boolean',
63                            'description' => __( 'Whether to test connections.', 'jetpack-publicize-pkg' ),
64                        ),
65                    ),
66                ),
67                array(
68                    'methods'             => WP_REST_Server::CREATABLE,
69                    'callback'            => array( $this, 'create_item' ),
70                    'permission_callback' => array( $this, 'create_item_permissions_check' ),
71                    'args'                => array(
72                        'keyring_connection_ID' => array(
73                            'description' => __( 'Keyring connection ID.', 'jetpack-publicize-pkg' ),
74                            'type'        => 'integer',
75                            'required'    => true,
76                        ),
77                        'external_user_ID'      => array(
78                            'description' => __( 'External User Id - in case of services like Facebook.', 'jetpack-publicize-pkg' ),
79                            'type'        => 'string',
80                        ),
81                        'shared'                => array(
82                            'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ),
83                            'type'        => 'boolean',
84                        ),
85                    ),
86                ),
87                'schema' => array( $this, 'get_public_item_schema' ),
88            )
89        );
90
91        register_rest_route(
92            $this->namespace,
93            '/' . $this->rest_base . '/(?P<connection_id>[0-9]+)',
94            array(
95                'args'   => array(
96                    'connection_id' => array(
97                        'description' => __( 'Unique identifier for the connection.', 'jetpack-publicize-pkg' ),
98                        'type'        => 'string',
99                        'required'    => true,
100                    ),
101                ),
102                array(
103                    'methods'             => WP_REST_Server::EDITABLE,
104                    'callback'            => array( $this, 'update_item' ),
105                    'permission_callback' => array( $this, 'update_item_permissions_check' ),
106                    'args'                => array(
107                        'shared' => array(
108                            'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ),
109                            'type'        => 'boolean',
110                        ),
111                    ),
112                ),
113                array(
114                    'methods'             => WP_REST_Server::DELETABLE,
115                    'callback'            => array( $this, 'delete_item' ),
116                    'permission_callback' => array( $this, 'delete_item_permissions_check' ),
117
118                ),
119                'schema' => array( $this, 'get_public_item_schema' ),
120            )
121        );
122    }
123
124    /**
125     * Schema for the endpoint.
126     *
127     * @return array
128     */
129    public function get_item_schema() {
130        if ( $this->schema ) {
131            return $this->add_additional_fields_schema( $this->schema );
132        }
133        $deprecated_fields = array(
134            'id'                   => array(
135                'type'        => 'string',
136                'description' => __( 'Unique identifier for the Jetpack Social connection.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
137                    /* translators: %s is the new field name */
138                    __( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
139                    'connection_id'
140                ),
141            ),
142            'username'             => array(
143                'type'        => 'string',
144                'description' => __( 'Username of the connected account.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
145                    /* translators: %s is the new field name */
146                    __( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
147                    'external_handle'
148                ),
149            ),
150            'profile_display_name' => array(
151                'type'        => 'string',
152                'description' => __( 'The name to display in the profile of the connected account.', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
153                    /* translators: %s is the new field name */
154                    __( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
155                    'display_name'
156                ),
157            ),
158            'global'               => array(
159                'type'        => 'boolean',
160                'description' => __( 'Is this connection available to all users?', 'jetpack-publicize-pkg' ) . ' ' . sprintf(
161                    /* translators: %s is the new field name */
162                    __( 'Deprecated in favor of %s.', 'jetpack-publicize-pkg' ),
163                    'shared'
164                ),
165            ),
166        );
167
168        $schema = array(
169            '$schema'    => 'http://json-schema.org/draft-04/schema#',
170            'title'      => 'jetpack-publicize-connection',
171            'type'       => 'object',
172            'properties' => array_merge(
173                $deprecated_fields,
174                self::get_the_item_schema()
175            ),
176        );
177
178        $this->schema = $schema;
179
180        return $this->add_additional_fields_schema( $schema );
181    }
182
183    /**
184     * Get the schema for the connection item.
185     *
186     * @return array
187     */
188    public static function get_the_item_schema() {
189        return array(
190            'connection_id'   => array(
191                'type'        => 'string',
192                'description' => __( 'Connection ID of the connected account.', 'jetpack-publicize-pkg' ),
193            ),
194            'display_name'    => array(
195                'type'        => 'string',
196                'description' => __( 'Display name of the connected account.', 'jetpack-publicize-pkg' ),
197            ),
198            'external_handle' => array(
199                'type'        => array( 'string', 'null' ),
200                'description' => __( 'The external handle or username of the connected account.', 'jetpack-publicize-pkg' ),
201            ),
202            'external_id'     => array(
203                'type'        => 'string',
204                'description' => __( 'The external ID of the connected account.', 'jetpack-publicize-pkg' ),
205            ),
206            'profile_link'    => array(
207                'type'        => 'string',
208                'description' => __( 'Profile link of the connected account.', 'jetpack-publicize-pkg' ),
209            ),
210            'profile_picture' => array(
211                'type'        => 'string',
212                'description' => __( 'URL of the profile picture of the connected account.', 'jetpack-publicize-pkg' ),
213            ),
214            'service_label'   => array(
215                'type'        => 'string',
216                'description' => __( 'Human-readable label for the Jetpack Social service.', 'jetpack-publicize-pkg' ),
217            ),
218            'service_name'    => array(
219                'type'        => 'string',
220                'description' => __( 'Alphanumeric identifier for the Jetpack Social service.', 'jetpack-publicize-pkg' ),
221            ),
222            'shared'          => array(
223                'type'        => 'boolean',
224                'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ),
225            ),
226            'status'          => array(
227                'type'        => array( 'string', 'null' ),
228                'description' => __( 'The connection status.', 'jetpack-publicize-pkg' ),
229                'enum'        => array(
230                    'ok',
231                    'broken',
232                    'must_reauth',
233                    null,
234                ),
235            ),
236            'wpcom_user_id'   => array(
237                'type'        => 'integer',
238                'description' => __( 'wordpress.com ID of the user the connection belongs to.', 'jetpack-publicize-pkg' ),
239            ),
240        );
241    }
242
243    /**
244     * Verify that the request has access to connectoins list.
245     *
246     * @param WP_REST_Request $request Full details about the request.
247     * @return true|WP_Error
248     */
249    public function get_items_permissions_check( $request ) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
250        return $this->publicize_permissions_check();
251    }
252
253    /**
254     * Get list of connected Publicize connections.
255     *
256     * @param WP_REST_Request $request Full details about the request.
257     *
258     * @return WP_REST_Response suitable for 1-page collection
259     */
260    public function get_items( $request ) {
261        if ( Publicize_Utils::is_wpcom() ) {
262            $args = array(
263                'context'          => self::is_authorized_blog_request() ? 'blog' : 'user',
264                'test_connections' => $request->get_param( 'test_connections' ),
265            );
266
267            $connections = Connections::wpcom_get_connections( $args );
268        } else {
269            $connections = $this->proxy_request_to_wpcom_as_user( $request );
270        }
271
272        if ( is_wp_error( $connections ) ) {
273            return $connections;
274        }
275
276        $items = array();
277
278        foreach ( $connections as $item ) {
279            $data = $this->prepare_item_for_response( $item, $request );
280
281            $items[] = $this->prepare_response_for_collection( $data );
282        }
283
284        $response = rest_ensure_response( $items );
285        $response->header( 'X-WP-Total', (string) count( $items ) );
286        $response->header( 'X-WP-TotalPages', '1' );
287
288        return $response;
289    }
290
291    /**
292     * Checks if a given request has access to create a connection.
293     *
294     * @param WP_REST_Request $request Full details about the request.
295     * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
296     */
297    public function create_item_permissions_check( $request ) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
298        $permissions = parent::publicize_permissions_check();
299
300        if ( is_wp_error( $permissions ) ) {
301            return $permissions;
302        }
303
304        return current_user_can( 'publish_posts' );
305    }
306
307    /**
308     * Creates a new connection.
309     *
310     * @param WP_REST_Request $request Full details about the request.
311     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
312     */
313    public function create_item( $request ) {
314        if ( Publicize_Utils::is_wpcom() ) {
315
316            $input = array(
317                'keyring_connection_ID' => $request->get_param( 'keyring_connection_ID' ),
318                'shared'                => $request->get_param( 'shared' ),
319            );
320
321            $external_user_id = $request->get_param( 'external_user_ID' );
322            if ( ! empty( $external_user_id ) ) {
323                $input['external_user_ID'] = $external_user_id;
324            }
325
326            $result = Connections::wpcom_create_connection( $input );
327
328            if ( is_wp_error( $result ) ) {
329                return $result;
330            }
331
332            $connection = Connections::get_by_id( $result );
333
334            $response = $this->prepare_item_for_response( $connection, $request );
335            $response = rest_ensure_response( $response );
336
337            $response->set_status( 201 );
338
339            return $response;
340
341        }
342
343        $response = $this->proxy_request_to_wpcom_as_user( $request, '', array( 'timeout' => 120 ) );
344
345        if ( is_wp_error( $response ) ) {
346            return new WP_Error(
347                'jp_connection_update_failed',
348                __( 'Something went wrong while creating a connection.', 'jetpack-publicize-pkg' ),
349                $response->get_error_message()
350            );
351        }
352
353        $response = rest_ensure_response( $response );
354
355        $response->set_status( 201 );
356
357        return $response;
358    }
359
360    /**
361     * Checks if a given request has access to update a connection.
362     *
363     * @param WP_REST_Request $request Full details about the request.
364     * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
365     */
366    public function update_item_permissions_check( $request ) {
367        $permissions = parent::publicize_permissions_check();
368
369        if ( is_wp_error( $permissions ) ) {
370            return $permissions;
371        }
372
373        // If the user cannot manage the connection, they can't update it either.
374        if ( ! $this->manage_connection_permission_check( $request ) ) {
375            return new WP_Error(
376                'rest_cannot_edit',
377                __( 'Sorry, you are not allowed to update this connection.', 'jetpack-publicize-pkg' ),
378                array( 'status' => rest_authorization_required_code() )
379            );
380        }
381
382        // If the connection is being marked/unmarked as shared.
383        if ( $request->has_param( 'shared' ) ) {
384            // Only editors and above can mark a connection as shared.
385            return current_user_can( 'edit_others_posts' );
386        }
387
388        return current_user_can( 'publish_posts' );
389    }
390
391    /**
392     * Update a connection.
393     *
394     * @param WP_REST_Request $request Full details about the request.
395     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
396     */
397    public function update_item( $request ) {
398        $connection_id = $request->get_param( 'connection_id' );
399
400        if ( Publicize_Utils::is_wpcom() ) {
401
402            $input = array(
403                'shared' => $request->get_param( 'shared' ),
404            );
405
406            $result = Connections::wpcom_update_connection( $connection_id, $input );
407
408            if ( is_wp_error( $result ) ) {
409                return $result;
410            }
411
412            $connection = Connections::get_by_id( $connection_id );
413
414            $response = $this->prepare_item_for_response( $connection, $request );
415            $response = rest_ensure_response( $response );
416
417            $response->set_status( 201 );
418
419            return $response;
420        }
421
422        $response = $this->proxy_request_to_wpcom_as_user( $request, $connection_id, array( 'timeout' => 120 ) );
423
424        if ( is_wp_error( $response ) ) {
425            return new WP_Error(
426                'jp_connection_updation_failed',
427                __( 'Something went wrong while updating the connection.', 'jetpack-publicize-pkg' ),
428                $response->get_error_message()
429            );
430        }
431
432        $response = rest_ensure_response( $response );
433
434        $response->set_status( 201 );
435
436        return $response;
437    }
438
439    /**
440     * Checks if a given request has access to delete a connection.
441     *
442     * @param WP_REST_Request $request Full details about the request.
443     * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
444     */
445    public function delete_item_permissions_check( $request ) {
446        $permissions = parent::publicize_permissions_check();
447
448        if ( is_wp_error( $permissions ) ) {
449            return $permissions;
450        }
451
452        return $this->manage_connection_permission_check( $request );
453    }
454
455    /**
456     * Delete a connection.
457     *
458     * @param WP_REST_Request $request Full details about the request.
459     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
460     */
461    public function delete_item( $request ) {
462        $connection_id = $request->get_param( 'connection_id' );
463
464        if ( Publicize_Utils::is_wpcom() ) {
465
466            $result = Connections::wpcom_delete_connection( $connection_id );
467
468            if ( is_wp_error( $result ) ) {
469                return $result;
470            }
471
472            $response = rest_ensure_response( $result );
473
474            $response->set_status( 201 );
475
476            return $response;
477        }
478
479        $response = $this->proxy_request_to_wpcom_as_user( $request, $connection_id, array( 'timeout' => 120 ) );
480
481        if ( is_wp_error( $response ) ) {
482            return new WP_Error(
483                'jp_connection_deletion_failed',
484                __( 'Something went wrong while deleting the connection.', 'jetpack-publicize-pkg' ),
485                $response->get_error_message()
486            );
487        }
488
489        $response = rest_ensure_response( $response );
490
491        $response->set_status( 201 );
492
493        return $response;
494    }
495}