Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
24.14% covered (danger)
24.14%
28 / 116
28.57% covered (danger)
28.57%
4 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Connections
24.14% covered (danger)
24.14%
28 / 116
28.57% covered (danger)
28.57%
4 / 14
889.24
0.00% covered (danger)
0.00%
0 / 1
 get_all
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 get_by_id
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 get_all_for_user
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 is_shared
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 user_owns_connection
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 fetch_and_cache_connections
37.50% covered (danger)
37.50%
3 / 8
0.00% covered (danger)
0.00%
0 / 1
7.91
 fetch_site_connections
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 wpcom_get_connections
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
 wpcom_prepare_connection_data
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
 wpcom_create_connection
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 wpcom_update_connection
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 wpcom_delete_connection
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 get_test_status
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 clear_cache
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Publicize Connections class.
4 *
5 * @package automattic/jetpack-publicize
6 */
7
8namespace Automattic\Jetpack\Publicize;
9
10use Automattic\Jetpack\Connection;
11use Automattic\Jetpack\Publicize\REST_API\Proxy_Requests;
12use WP_Error;
13use WP_REST_Request;
14
15/**
16 * Publicize Connections class.
17 */
18class Connections {
19
20    const CONNECTIONS_TRANSIENT = 'jetpack_social_connections_list';
21
22    /**
23     * Get all connections.
24     *
25     * @param array $args Arguments
26     *                - 'ignore_cache': bool Whether to ignore the cache and fetch the connections from the API.
27     * @return array
28     */
29    public static function get_all( $args = array() ) {
30
31        if ( Publicize_Utils::is_wpcom() ) {
32            $connections = self::wpcom_get_connections( array( 'context' => 'blog' ) );
33        } else {
34
35            $ignore_cache = $args['ignore_cache'] ?? false;
36
37            $connections = get_transient( self::CONNECTIONS_TRANSIENT );
38
39            if ( $ignore_cache || false === $connections ) {
40                $connections = self::fetch_and_cache_connections();
41            }
42        }
43
44        return $connections;
45    }
46
47    /**
48     * Get a connection by connection_id.
49     *
50     * @param string $connection_id Connection ID.
51     *
52     * @return array|null
53     */
54    public static function get_by_id( $connection_id ) {
55
56        $connections = self::get_all();
57
58        foreach ( $connections as $connection ) {
59            if ( $connection['connection_id'] === $connection_id ) {
60                return $connection;
61            }
62        }
63
64        return null;
65    }
66
67    /**
68     * Get all connections for the current user.
69     *
70     * @param array $args Arguments. Same as self::get_all().
71     *
72     * @see Automattic\Jetpack\Publicize\Connections::get_all()
73     *
74     * @return array
75     */
76    public static function get_all_for_user( $args = array() ) {
77        $connections = self::get_all( $args );
78
79        $connections_for_user = array();
80
81        foreach ( $connections as $connection ) {
82
83            if ( self::is_shared( $connection ) || self::user_owns_connection( $connection ) ) {
84                $connections_for_user[] = $connection;
85            }
86        }
87
88        return $connections_for_user;
89    }
90
91    /**
92     * Check whether a connection is shared.
93     *
94     * @param array $connection The connection.
95     *
96     * @return boolean
97     */
98    public static function is_shared( $connection ) {
99        return ! empty( $connection['shared'] );
100    }
101
102    /**
103     * Whether the current user owns a connection.
104     *
105     * @param array $connection The connection.
106     * @param int   $user_id    The user ID. Defaults to the current user.
107     *
108     * @return bool
109     */
110    public static function user_owns_connection( $connection, $user_id = null ) {
111        if ( Publicize_Utils::is_wpcom() ) {
112            $wpcom_user_id = get_current_user_id();
113        } else {
114
115            $wpcom_user_data = ( new Connection\Manager() )->get_connected_user_data( $user_id );
116
117            $wpcom_user_id = ! empty( $wpcom_user_data['ID'] ) ? $wpcom_user_data['ID'] : null;
118        }
119
120        return $wpcom_user_id && $connection['wpcom_user_id'] === $wpcom_user_id;
121    }
122
123    /**
124     * Fetch connections from the REST API and cache them.
125     *
126     * @return array
127     */
128    public static function fetch_and_cache_connections() {
129        $connections = self::fetch_site_connections();
130
131        if ( is_wp_error( $connections ) ) {
132            // @todo log error.
133            return array();
134        }
135
136        if ( is_array( $connections ) ) {
137            if ( ! set_transient( self::CONNECTIONS_TRANSIENT, $connections, HOUR_IN_SECONDS * 4 ) ) {
138                // If the transient has beeen set in another request, the call to set_transient can fail.
139                // If so, we can delete the transient and try again.
140                self::clear_cache();
141
142                set_transient( self::CONNECTIONS_TRANSIENT, $connections, HOUR_IN_SECONDS * 4 );
143            }
144        }
145
146        return $connections;
147    }
148
149    /**
150     * Fetch connections for the site from WPCOM REST API.
151     *
152     * @return array|WP_Error
153     */
154    public static function fetch_site_connections() {
155        $proxy = new Proxy_Requests( 'publicize/connections' );
156
157        $request = new WP_REST_Request( 'GET' );
158
159        return $proxy->proxy_request_to_wpcom_as_blog( $request );
160    }
161
162    /**
163     * Get all connections. Meant to be called directly only on WPCOM.
164     *
165     * @param array $args Arguments
166     *                    - 'test_connections': bool Whether to run connection tests.
167     *                    - 'context': enum('blog', 'user') Whether to include connections for the current blog or user.
168     *
169     * @return array
170     */
171    public static function wpcom_get_connections( $args = array() ) {
172        // Ensure that we are on WPCOM.
173        Publicize_Utils::assert_is_wpcom( __METHOD__ );
174
175        /**
176         * Publicize instance.
177         */
178        global $publicize;
179
180        $items = array();
181
182        $run_tests = $args['test_connections'] ?? false;
183
184        $test_results = $run_tests ? self::get_test_status() : array();
185
186        $service_connections = $publicize->get_all_connections_for_blog_id( get_current_blog_id() );
187
188        $context = $args['context'] ?? 'user';
189
190        foreach ( $service_connections as $service_name => $connections ) {
191            foreach ( $connections as $connection ) {
192                $connection_id = $publicize->get_connection_id( $connection );
193
194                $item = self::wpcom_prepare_connection_data( $connection, $service_name );
195
196                $item['status'] = $test_results[ $connection_id ] ?? null;
197
198                // For blog context, return all connections.
199                // Otherwise, return only connections owned by the user and the shared ones.
200                if ( 'blog' === $context || $item['shared'] || self::user_owns_connection( $item ) ) {
201                    $items[] = $item;
202                }
203            }
204        }
205
206        return $items;
207    }
208
209    /**
210     * Filters out data based on ?_fields= request parameter
211     *
212     * @param mixed  $connection   Connection to prepare.
213     * @param string $service_name Service name.
214     *
215     * @return array
216     */
217    public static function wpcom_prepare_connection_data( $connection, $service_name ) {
218        // Ensure that we are on WPCOM.
219        Publicize_Utils::assert_is_wpcom( __METHOD__ );
220
221        /**
222         * Publicize instance.
223         */
224        global $publicize;
225
226        $connection_id = $publicize->get_connection_id( $connection );
227
228        $connection_meta = $publicize->get_connection_meta( $connection );
229        $connection_data = $connection_meta['connection_data'];
230
231        $row_meta = $connection_data['meta'] ?? array();
232
233        return array(
234            'connection_id'        => (string) $connection_id,
235            'display_name'         => (string) $publicize->get_display_name( $service_name, $connection ),
236            'external_handle'      => (string) $publicize->get_external_handle( $service_name, $connection ),
237            'external_id'          => $connection_meta['external_id'] ?? '',
238            'profile_link'         => (string) $publicize->get_profile_link( $service_name, $connection ),
239            'profile_picture'      => (string) $publicize->get_profile_picture( $connection ),
240            'service_label'        => (string) Publicize::get_service_label( $service_name ),
241            'service_name'         => $service_name,
242            'shared'               => ! $connection_data['user_id'],
243            'template'             => (string) ( $row_meta['template'] ?? '' ),
244            'wpcom_user_id'        => (int) $connection_data['user_id'],
245
246            // Deprecated fields.
247            'id'                   => (string) $publicize->get_connection_unique_id( $connection ),
248            'username'             => $publicize->get_username( $service_name, $connection ),
249            'profile_display_name' => ! empty( $connection_meta['profile_display_name'] ) ? $connection_meta['profile_display_name'] : '',
250            // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- We expect an integer, but do loose comparison below in case some other type is stored.
251            'global'               => 0 == $connection_data['user_id'],
252
253        );
254    }
255
256    /**
257     * Create a connection. Meant to be called directly only on WPCOM.
258     *
259     * @param mixed $input Input data.
260     *
261     * @return string|WP_Error Connection ID or WP_Error.
262     */
263    public static function wpcom_create_connection( $input ) {
264        // Ensure that we are on WPCOM.
265        Publicize_Utils::assert_is_wpcom( __METHOD__ );
266
267        require_lib( 'social-connections-rest-helper' );
268
269        $connections_helper = \Social_Connections_Rest_Helper::init();
270
271        $result = $connections_helper->create_publicize_connection( $input );
272
273        if ( is_wp_error( $result ) ) {
274            return $result;
275        }
276
277        if ( ! isset( $result['ID'] ) ) {
278            return new WP_Error(
279                'wpcom_connection_creation_failed',
280                __( 'Something went wrong while creating a connection.', 'jetpack-publicize-pkg' )
281            );
282        }
283
284        return (string) $result['ID'];
285    }
286
287    /**
288     * Update a connection. Meant to be called directly only on WPCOM.
289     *
290     * @param string $connection_id Connection ID.
291     * @param mixed  $input Input data.
292     *
293     * @return string|WP_Error Connection ID or WP_Error.
294     */
295    public static function wpcom_update_connection( $connection_id, $input ) {
296        // Ensure that we are on WPCOM.
297        Publicize_Utils::assert_is_wpcom( __METHOD__ );
298
299        require_lib( 'social-connections-rest-helper' );
300        $connections_helper = \Social_Connections_Rest_Helper::init();
301
302        $result = $connections_helper->update_connection( $connection_id, $input );
303
304        if ( is_wp_error( $result ) ) {
305            return $result;
306        }
307
308        if ( ! $result ) {
309            return new WP_Error(
310                'wpcom_connection_updation_failed',
311                __( 'Something went wrong while updating the connection.', 'jetpack-publicize-pkg' )
312            );
313        }
314
315        return (string) $connection_id;
316    }
317
318    /**
319     * Delete a connection. Meant to be called directly only on WPCOM.
320     *
321     * @param string $connection_id Connection ID.
322     *
323     * @return bool|WP_Error
324     */
325    public static function wpcom_delete_connection( $connection_id ) {
326        // Ensure that we are on WPCOM.
327        Publicize_Utils::assert_is_wpcom( __METHOD__ );
328
329        require_lib( 'social-connections-rest-helper' );
330        $connections_helper = \Social_Connections_Rest_Helper::init();
331
332        $result = $connections_helper->delete_publicize_connection( $connection_id );
333
334        if ( is_wp_error( $result ) ) {
335            return $result;
336        }
337
338        if ( ! $result ) {
339            return new WP_Error(
340                'wpcom_connection_deletion_failed',
341                __( 'Something went wrong while deleting the connection.', 'jetpack-publicize-pkg' )
342            );
343        }
344
345        return true;
346    }
347
348    /**
349     * Get the connections test status.
350     *
351     * @return array
352     */
353    public static function get_test_status() {
354        /**
355         * Publicize instance.
356         *
357         * @var \Automattic\Jetpack\Publicize\Publicize $publicize
358         */
359        global $publicize;
360
361        $test_results = $publicize->get_publicize_conns_test_results();
362
363        $test_results_map = array();
364
365        foreach ( $test_results as $test_result ) {
366            $result = $test_result['connectionTestPassed'];
367            if ( 'must_reauth' !== $result ) {
368                $result = $test_result['connectionTestPassed'] ? 'ok' : 'broken';
369            }
370            $test_results_map[ $test_result['connectionID'] ] = $result;
371        }
372
373        return $test_results_map;
374    }
375
376    /**
377     * Clear the connections cache.
378     */
379    public static function clear_cache() {
380        delete_transient( self::CONNECTIONS_TRANSIENT );
381    }
382}