Completed
Push — pr/7794 ( cb7d3e...693e58 )
by George
09:28
created

Jetpack_Client_Server   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 283
Duplicated Lines 2.47 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 7
loc 283
rs 8.3206
c 0
b 0
f 0
wmc 51
lcom 1
cbo 7

6 Methods

Rating   Name   Duplication   Size   Complexity  
B client_authorize() 0 37 4
D authorize() 7 108 19
A deactivate_plugin() 0 19 4
D get_token() 0 99 22
A get_jetpack() 0 3 1
A do_exit() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Client_Server often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Client_Server, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Client = Plugin
5
 * Client Server = API Methods the Plugin must respond to
6
 */
7
class Jetpack_Client_Server {
8
9
	/**
10
	 * Authorizations
11
	 */
12
	function client_authorize() {
13
		$data              = stripslashes_deep( $_GET );
14
		$data['auth_type'] = 'client';
15
		$role              = Jetpack::translate_current_user_to_role();
16
		$redirect          = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
17
18
		check_admin_referer( "jetpack-authorize_{$role}_{$redirect}" );
19
20
		$result = $this->authorize( $data );
21
		if ( is_wp_error( $result ) ) {
22
			Jetpack::state( 'error', $result->get_error_code() );
23
			JetpackTracking::record_user_event( 'jpc_client_authorize_fail', array(
24
				'error_code' => $result->get_error_code(),
25
				'error_message' => $result->get_error_message()
26
			) );
27
		} else {
28
			/**
29
			 * Fires after the Jetpack client is authorized to communicate with WordPress.com.
30
			 *
31
			 * @since 4.2.0
32
			 *
33
			 * @param int Jetpack Blog ID.
34
			 */
35
			do_action( 'jetpack_client_authorized', Jetpack_Options::get_option( 'id' ) );
36
			JetpackTracking::record_user_event( 'jpc_client_authorize_success' );
37
		}
38
39
		if ( wp_validate_redirect( $redirect ) ) {
40
			// Exit happens below in $this->do_exit()
41
			wp_safe_redirect( $redirect );
42
		} else {
43
			// Exit happens below in $this->do_exit()
44
			wp_safe_redirect( Jetpack::admin_url() );
45
		}
46
47
		$this->do_exit();
48
	}
49
50
	function authorize( $data = array() ) {
51
		$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
0 ignored issues
show
Unused Code introduced by
$redirect is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
52
53
		$jetpack_unique_connection = Jetpack_Options::get_option( 'unique_connection' );
54
		// Checking if site has been active/connected previously before recording unique connection
55
		if ( ! $jetpack_unique_connection ) {
56
			// jetpack_unique_connection option has never been set
57
			$jetpack_unique_connection = array(
58
				'connected'     => 0,
59
				'disconnected'  => 0,
60
				'version'       => '3.6.1',
61
			);
62
63
			update_option( 'jetpack_unique_connection', $jetpack_unique_connection );
64
65
			//track unique connection
66
			$jetpack = $this->get_jetpack();;
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
67
68
			$jetpack->stat( 'connections', 'unique-connection' );
69
			$jetpack->do_stats( 'server_side' );
70
		}
71
72
		// increment number of times connected
73
		$jetpack_unique_connection['connected'] += 1;
74
		Jetpack_Options::update_option( 'unique_connection', $jetpack_unique_connection );
75
76
		$role = Jetpack::translate_current_user_to_role();
77
78
		if ( ! $role ) {
79
			return new Jetpack_Error( 'no_role', 'Invalid request.', 400 );
80
		}
81
82
		$cap = Jetpack::translate_role_to_cap( $role );
83
		if ( ! $cap ) {
84
			return new Jetpack_Error( 'no_cap', 'Invalid request.', 400 );
85
		}
86
87
		if ( ! empty( $data['error'] ) ) {
88
			return new Jetpack_Error( $data['error'], 'Error included in the request.', 400 );
89
		}
90
91
		if ( ! isset( $data['state'] ) ) {
92
			return new Jetpack_Error( 'no_state', 'Request must include state.', 400 );
93
		}
94
95
		if ( ! ctype_digit( $data['state'] ) ) {
96
			return new Jetpack_Error( $data['error'], 'State must be an integer.', 400 );
97
		}
98
99
		$current_user_id = get_current_user_id();
100
		if ( $current_user_id != $data['state'] ) {
101
			return new Jetpack_Error( 'wrong_state', 'State does not match current user.', 400 );
102
		}
103
104
		if ( empty( $data['code'] ) ) {
105
			return new Jetpack_Error( 'no_code', 'Request must include an authorization code.', 400 );
106
		}
107
108
		$token = $this->get_token( $data );
109
110
		if ( is_wp_error( $token ) ) {
111
			$code = $token->get_error_code();
112
			if ( empty( $code ) ) {
113
				$code = 'invalid_token';
114
			}
115
			return new Jetpack_Error( $code, $token->get_error_message(), 400 );
116
		}
117
118
		if ( ! $token ) {
119
			return new Jetpack_Error( 'no_token', 'Error generating token.', 400 );
120
		}
121
122
		$is_master_user = ! Jetpack::is_active();
123
124
		Jetpack::update_user_token( $current_user_id, sprintf( '%s.%d', $token, $current_user_id ), $is_master_user );
125
126
		if ( ! $is_master_user ) {
127
			Jetpack::state( 'message', 'linked' );
128
			// Don't activate anything since we are just connecting a user.
129
			return 'linked';
130
		}
131
132
		$redirect_on_activation_error = ( 'client' === $data['auth_type'] ) ? true : false;
133 View Code Duplication
		if ( $active_modules = Jetpack_Options::get_option( 'active_modules' ) ) {
134
			Jetpack::delete_active_modules();
135
136
			Jetpack::activate_default_modules( 999, 1, $active_modules, $redirect_on_activation_error, false );
0 ignored issues
show
Documentation introduced by
999 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
137
		} else {
138
			Jetpack::activate_default_modules( false, false, array(), $redirect_on_activation_error, false );
139
		}
140
141
		// If redirect_uri is SSO, ensure SSO module is enabled
142
		parse_str( parse_url( $data['redirect_uri'], PHP_URL_QUERY ), $redirect_options );
143
		/** This filter is documented in class.jetpack-cli.php */
144
		if ( isset( $redirect_options['action'] ) && 'jetpack-sso' === $redirect_options['action'] && apply_filters( 'jetpack_start_enable_sso', true ) ) {
145
			Jetpack::activate_module( 'sso', false, false );
146
		}
147
148
		// Since this is a fresh connection, be sure to clear out IDC options
149
		Jetpack_IDC::clear_all_idc_options();
150
151
		// Start nonce cleaner
152
		wp_clear_scheduled_hook( 'jetpack_clean_nonces' );
153
		wp_schedule_event( time(), 'hourly', 'jetpack_clean_nonces' );
154
155
		Jetpack::state( 'message', 'authorized' );
156
		return 'authorized';
157
	}
158
159
	public static function deactivate_plugin( $probable_file, $probable_title ) {
160
		include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
161
		if ( is_plugin_active( $probable_file ) ) {
162
			deactivate_plugins( $probable_file );
163
			return 1;
164
		} else {
165
			// If the plugin is not in the usual place, try looking through all active plugins.
166
			$active_plugins = Jetpack::get_active_plugins();
167
			foreach ( $active_plugins as $plugin ) {
168
				$data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
169
				if ( $data['Name'] == $probable_title ) {
170
					deactivate_plugins( $plugin );
171
					return 1;
172
				}
173
			}
174
		}
175
176
		return 0;
177
	}
178
179
	/**
180
	 * @return object|WP_Error
181
	 */
182
	function get_token( $data ) {
183
		$role = Jetpack::translate_current_user_to_role();
184
185
		if ( ! $role ) {
186
			return new Jetpack_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack' ) );
187
		}
188
189
		$client_secret = Jetpack_Data::get_access_token();
190
		if ( ! $client_secret ) {
191
			return new Jetpack_Error( 'client_secret', __( 'You need to register your Jetpack before connecting it.', 'jetpack' ) );
192
		}
193
194
		$redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : '';
195
		$redirect_uri = ( 'calypso' === $data['auth_type'] )
196
			? $data['redirect_uri']
197
			: add_query_arg( array(
198
				'action' => 'authorize',
199
				'_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ),
200
				'redirect' => $redirect ? urlencode( $redirect ) : false,
201
			), menu_page_url( 'jetpack', false ) );
202
203
		$body = array(
204
			'client_id' => Jetpack_Options::get_option( 'id' ),
205
			'client_secret' => $client_secret->secret,
206
			'grant_type' => 'authorization_code',
207
			'code' => $data['code'],
208
			'redirect_uri' => $redirect_uri,
209
		);
210
211
		$args = array(
212
			'method' => 'POST',
213
			'body' => $body,
214
			'headers' => array(
215
				'Accept' => 'application/json',
216
			),
217
		);
218
		$response = Jetpack_Client::_wp_remote_request( Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'token' ) ), $args );
219
220
		if ( is_wp_error( $response ) ) {
221
			return new Jetpack_Error( 'token_http_request_failed', $response->get_error_message() );
222
		}
223
224
		$code = wp_remote_retrieve_response_code( $response );
225
		$entity = wp_remote_retrieve_body( $response );
226
227
		if ( $entity ) {
228
			$json = json_decode( $entity );
229
		} else {
230
			$json = false;
231
		}
232
233
		if ( 200 != $code || ! empty( $json->error ) ) {
234
			if ( empty( $json->error ) ) {
235
				return new Jetpack_Error( 'unknown', '', $code );
236
			}
237
238
			$error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : '';
239
240
			return new Jetpack_Error( (string) $json->error, $error_description, $code );
241
		}
242
243
		if ( empty( $json->access_token ) || ! is_scalar( $json->access_token ) ) {
244
			return new Jetpack_Error( 'access_token', '', $code );
245
		}
246
247
		if ( empty( $json->token_type ) || 'X_JETPACK' != strtoupper( $json->token_type ) ) {
248
			return new Jetpack_Error( 'token_type', '', $code );
249
		}
250
251
		if ( empty( $json->scope ) ) {
252
			return new Jetpack_Error( 'scope', 'No Scope', $code );
253
		}
254
255
		@list( $role, $hmac ) = explode( ':', $json->scope );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
256
		if ( empty( $role ) || empty( $hmac ) ) {
257
			return new Jetpack_Error( 'scope', 'Malformed Scope', $code );
258
		}
259
260
		if ( Jetpack::sign_role( $role ) !== $json->scope ) {
261
			return new Jetpack_Error( 'scope', 'Invalid Scope', $code );
262
		}
263
264
		if ( ! $cap = Jetpack::translate_role_to_cap( $role ) ) {
265
			return new Jetpack_Error( 'scope', 'No Cap', $code );
266
		}
267
268
		if ( ! current_user_can( $cap ) ) {
269
			return new Jetpack_Error( 'scope', 'current_user_cannot', $code );
270
		}
271
272
		/**
273
		 * Fires after user has successfully received an auth token.
274
		 *
275
		 * @since 3.9.0
276
		 */
277
		do_action( 'jetpack_user_authorized' );
278
279
		return (string) $json->access_token;
280
	}
281
282
	public function get_jetpack() {
283
		return Jetpack::init();
284
	}
285
286
	public function do_exit() {
287
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method do_exit() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
288
	}
289
}
290