Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
18.39% covered (danger)
18.39%
16 / 87
18.75% covered (danger)
18.75%
3 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
Connection
18.39% covered (danger)
18.39%
16 / 87
18.75% covered (danger)
18.75%
3 / 16
588.57
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 init
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 initialize_deactivate_disconnect
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 deactivate_disconnect
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 deactivate_disconnect_network
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 wpcom_blog_id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 is_connected
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 register
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 disconnect
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 register_rest_routes
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
2
 create_connection_endpoint
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 get_connection_endpoint
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_connection_api_response
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 can_manage_connection
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 rest_authorization_required_code
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 ensure_connection
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Jetpack connection client.
4 *
5 * @link       https://automattic.com
6 * @since      1.0.0
7 * @package    automattic/jetpack-boost
8 */
9
10namespace Automattic\Jetpack_Boost\Lib;
11
12use Automattic\Jetpack\Config as Jetpack_Config;
13use Automattic\Jetpack\Connection\Manager;
14use Automattic\Jetpack\Terms_Of_Service;
15
16/**
17 * Class Connection
18 *
19 * Manages the Jetpack connection on behalf of Jetpack Boost.
20 */
21class Connection {
22
23    /**
24     * Jetpack Connection Manager.
25     *
26     * @var Manager $manager The connection manager.
27     */
28    private $manager;
29
30    public function __construct() {
31        $this->manager = new Manager( 'jetpack-boost' );
32    }
33
34    public function init() {
35        add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
36
37        $this->initialize_deactivate_disconnect();
38    }
39
40    /**
41     * Initialize the plugin deactivation hook.
42     */
43    public function initialize_deactivate_disconnect() {
44        require_once ABSPATH . '/wp-admin/includes/plugin.php';
45
46        if ( is_plugin_active_for_network( JETPACK_BOOST_PATH ) ) {
47            register_deactivation_hook( JETPACK_BOOST_PATH, array( $this, 'deactivate_disconnect_network' ) );
48        } else {
49            register_deactivation_hook( JETPACK_BOOST_PATH, array( $this, 'deactivate_disconnect' ) );
50        }
51    }
52
53    /**
54     * Deactivate the connection on plugin disconnect.
55     */
56    public function deactivate_disconnect() {
57        $this->manager->remove_connection();
58    }
59
60    /**
61     * Deactivate the connection on plugin disconnect for network-activated plugins.
62     */
63    public function deactivate_disconnect_network() {
64        if ( ! is_network_admin() ) {
65            return;
66        }
67
68        foreach ( get_sites() as $s ) {
69            switch_to_blog( (int) $s->blog_id );
70
71            $active_plugins = get_option( 'active_plugins' );
72
73            /*
74             * If this plugin was activated in the subsite individually
75             * we do not want to call disconnect. Plugins activated
76             * individually (before network activation) stay activated
77             * when the network deactivation occurs
78             */
79            if ( ! in_array( JETPACK_BOOST_PATH, $active_plugins, true ) ) {
80                $this->deactivate_disconnect();
81            }
82
83            restore_current_blog();
84        }
85    }
86
87    /**
88     * Connection Lifecycle methods.
89     */
90
91    /**
92     * Get the WordPress.com blog ID of this site, if it's connected
93     */
94    public static function wpcom_blog_id() {
95        return defined( 'IS_WPCOM' ) && IS_WPCOM ? get_current_blog_id() : (int) \Jetpack_Options::get_option( 'id' );
96    }
97
98    /**
99     * True if the site is connected to WP.com.
100     *
101     * @return boolean
102     */
103    public function is_connected() {
104        /**
105         * Filter that fakes the connection to WordPress.com. Useful for testing.
106         *
107         * @param bool $connection Return true to fake the connection.
108         *
109         * @since   1.0.0
110         */
111        if ( true === apply_filters( 'jetpack_boost_connection_bypass', false ) ) {
112            return true;
113        }
114
115        return $this->manager->is_connected();
116    }
117
118    /**
119     * Register site using connection manager.
120     *
121     * @return true|\WP_Error The error object.
122     */
123    public function register() {
124        if ( $this->is_connected() ) {
125            Analytics::record_user_event( 'using_existing_connection' );
126            return true;
127        }
128
129        $result = $this->manager->register();
130
131        if ( ! is_wp_error( $result ) ) {
132            Analytics::record_user_event( 'established_connection' );
133            Premium_Features::clear_cache();
134        }
135
136        return $result;
137    }
138
139    /**
140     * Disconnect from Jetpack account.
141     *
142     * @return bool
143     */
144    public function disconnect() {
145        // @todo implement check for Jetpack::validate_sync_error_idc_option() so we don't disconnect production site from staging etc.
146        Analytics::record_user_event( 'disconnect_site' );
147
148        $this->manager->remove_connection();
149
150        return true;
151    }
152
153    /**
154     * REST endpoint methods.
155     */
156    public function register_rest_routes() {
157        register_rest_route(
158            JETPACK_BOOST_REST_NAMESPACE,
159            JETPACK_BOOST_REST_PREFIX . '/connection',
160            array(
161                'methods'             => \WP_REST_Server::READABLE,
162                'callback'            => array( $this, 'get_connection_endpoint' ),
163                'permission_callback' => array( $this, 'can_manage_connection' ),
164            )
165        );
166
167        register_rest_route(
168            JETPACK_BOOST_REST_NAMESPACE,
169            JETPACK_BOOST_REST_PREFIX . '/connection',
170            array(
171                'methods'             => \WP_REST_Server::EDITABLE,
172                'callback'            => array( $this, 'create_connection_endpoint' ),
173                'permission_callback' => array( $this, 'can_manage_connection' ),
174            )
175        );
176    }
177
178    /**
179     * Register site using connection manager.
180     *
181     * @param \WP_REST_Request $request The request object.
182     *
183     * @return \WP_REST_Response|\WP_Error
184     */
185    public function create_connection_endpoint( \WP_REST_Request $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
186
187        ( new Terms_Of_Service() )->agree();
188
189        $response = $this->register();
190
191        // Clear premium features cache to force a refresh.
192        Premium_Features::clear_cache();
193
194        if ( is_wp_error( $response ) ) {
195            return $response;
196        }
197
198        do_action( 'jetpack_boost_connection_established' );
199
200        return rest_ensure_response( $this->get_connection_api_response() );
201    }
202
203    /**
204     * Fetch connection info.
205     *
206     * @param \WP_REST_Request $request The request object.
207     *
208     * @return \WP_REST_Response|\WP_Error
209     */
210    public function get_connection_endpoint( \WP_REST_Request $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
211        return rest_ensure_response( $this->get_connection_api_response() );
212    }
213
214    /**
215     * Connection state sent to client on initialization and after updates.
216     *
217     * @return array
218     */
219    public function get_connection_api_response() {
220        /**
221         * Filter that fakes the connection to WordPress.com. Useful for testing.
222         *
223         * @param bool $connection Return true to fake the connection.
224         *
225         * @since   1.0.0
226         */
227        $force_connected = apply_filters( 'jetpack_boost_connection_bypass', false );
228
229        $response = array(
230            'connected'     => $force_connected || $this->is_connected(),
231            'wpcomBlogId'   => ( $force_connected || $this->is_connected() ) ? self::wpcom_blog_id() : null,
232            'userConnected' => $this->manager->is_user_connected(),
233        );
234
235        return $response;
236    }
237
238    /**
239     * Can user manage the connection?
240     *
241     * @return boolean | \WP_Error
242     */
243    public function can_manage_connection() {
244        if ( current_user_can( 'manage_options' ) ) {
245            return true;
246        }
247
248        $user_permissions_error_msg = __(
249            'You do not have the correct user permissions to perform this action.
250            Please contact your site admin if you think this is a mistake.',
251            'jetpack-boost'
252        );
253
254        return new \WP_Error(
255            'invalid_user_permission_jetpack_connect',
256            $user_permissions_error_msg,
257            array( 'status' => self::rest_authorization_required_code() )
258        );
259    }
260
261    /**
262     * Contextual HTTP error code for authorization failure.
263     *
264     * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
265     *
266     * @see   https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
267     *
268     * @since 4.3.0
269     *
270     * @return int
271     */
272    public static function rest_authorization_required_code() {
273        return is_user_logged_in() ? 403 : 401;
274    }
275
276    public function ensure_connection() {
277        /**
278         * Filter that fakes the connection to WordPress.com. Useful for testing.
279         *
280         * @param bool $connection Return true to fake the connection.
281         *
282         * @since   1.0.0
283         */
284        if ( ! apply_filters( 'jetpack_boost_connection_bypass', false ) ) {
285            $jetpack_config = new Jetpack_Config();
286            $jetpack_config->ensure(
287                'connection',
288                array(
289                    'slug'     => 'jetpack-boost',
290                    'name'     => 'Jetpack Boost',
291                    'url_info' => '', // Optional, URL of the plugin.
292                )
293            );
294        }
295    }
296}