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