Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
7.69% covered (danger)
7.69%
6 / 78
13.33% covered (danger)
13.33%
2 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
Connection
7.69% covered (danger)
7.69%
6 / 78
13.33% covered (danger)
13.33%
2 / 15
737.87
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 / 9
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
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
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            Performance_History_Entry::clear_cache();
135        }
136
137        return $result;
138    }
139
140    /**
141     * Disconnect from Jetpack account.
142     *
143     * @return bool
144     */
145    public function disconnect() {
146        // @todo implement check for Jetpack::validate_sync_error_idc_option() so we don't disconnect production site from staging etc.
147        Analytics::record_user_event( 'disconnect_site' );
148
149        $this->manager->remove_connection();
150
151        return true;
152    }
153
154    /**
155     * REST endpoint methods.
156     */
157    public function register_rest_routes() {
158        register_rest_route(
159            JETPACK_BOOST_REST_NAMESPACE,
160            JETPACK_BOOST_REST_PREFIX . '/connection',
161            array(
162                'methods'             => \WP_REST_Server::READABLE,
163                'callback'            => array( $this, 'get_connection_endpoint' ),
164                'permission_callback' => array( $this, 'can_manage_connection' ),
165            )
166        );
167
168        register_rest_route(
169            JETPACK_BOOST_REST_NAMESPACE,
170            JETPACK_BOOST_REST_PREFIX . '/connection',
171            array(
172                'methods'             => \WP_REST_Server::EDITABLE,
173                'callback'            => array( $this, 'create_connection_endpoint' ),
174                'permission_callback' => array( $this, 'can_manage_connection' ),
175            )
176        );
177    }
178
179    /**
180     * Register site using connection manager.
181     *
182     * @param \WP_REST_Request $request The request object.
183     *
184     * @return \WP_REST_Response|\WP_Error
185     */
186    public function create_connection_endpoint( \WP_REST_Request $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
187
188        ( new Terms_Of_Service() )->agree();
189
190        $response = $this->register();
191
192        // Clear premium features cache to force a refresh.
193        Premium_Features::clear_cache();
194
195        if ( is_wp_error( $response ) ) {
196            return $response;
197        }
198
199        do_action( 'jetpack_boost_connection_established' );
200
201        return rest_ensure_response( $this->get_connection_api_response() );
202    }
203
204    /**
205     * Fetch connection info.
206     *
207     * @param \WP_REST_Request $request The request object.
208     *
209     * @return \WP_REST_Response|\WP_Error
210     */
211    public function get_connection_endpoint( \WP_REST_Request $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
212        return rest_ensure_response( $this->get_connection_api_response() );
213    }
214
215    /**
216     * Connection state sent to client on initialization and after updates.
217     *
218     * @return array
219     */
220    public function get_connection_api_response() {
221        /**
222         * Filter that fakes the connection to WordPress.com. Useful for testing.
223         *
224         * @param bool $connection Return true to fake the connection.
225         *
226         * @since   1.0.0
227         */
228        $force_connected = apply_filters( 'jetpack_boost_connection_bypass', false );
229
230        $response = array(
231            'connected'     => $force_connected || $this->is_connected(),
232            'wpcomBlogId'   => ( $force_connected || $this->is_connected() ) ? self::wpcom_blog_id() : null,
233            'userConnected' => $this->manager->is_user_connected(),
234        );
235
236        return $response;
237    }
238
239    /**
240     * Can user manage the connection?
241     *
242     * @return boolean | \WP_Error
243     */
244    public function can_manage_connection() {
245        if ( current_user_can( 'manage_options' ) ) {
246            return true;
247        }
248
249        $user_permissions_error_msg = __(
250            'You do not have the correct user permissions to perform this action.
251            Please contact your site admin if you think this is a mistake.',
252            'jetpack-boost'
253        );
254
255        return new \WP_Error(
256            'invalid_user_permission_jetpack_connect',
257            $user_permissions_error_msg,
258            array( 'status' => self::rest_authorization_required_code() )
259        );
260    }
261
262    /**
263     * Contextual HTTP error code for authorization failure.
264     *
265     * Taken from rest_authorization_required_code() in WP-API plugin until is added to core.
266     *
267     * @see   https://github.com/WP-API/WP-API/commit/7ba0ae6fe4f605d5ffe4ee85b1cd5f9fb46900a6
268     *
269     * @since 4.3.0
270     *
271     * @return int
272     */
273    public static function rest_authorization_required_code() {
274        return is_user_logged_in() ? 403 : 401;
275    }
276}