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