MonsterInsights_API_Request::is_blocked()   C
last analyzed

Complexity

Conditions 17
Paths 17

Size

Total Lines 55
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 17
eloc 40
c 2
b 0
f 0
nc 17
nop 1
dl 0
loc 55
rs 5.2166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * API Request class.
5
 *
6
 * @since 7.0.0
7
 *
8
 * @package MonsterInsights
9
 * @author  Chris Christoff
10
 */
11
final class MonsterInsights_API_Request {
12
13
	/**
14
	 * Base API route.
15
	 *
16
	 * @since 7.0.0
17
	 *
18
	 * @var string
19
	 */
20
	public $base = 'api.monsterinsights.com/v2/';
21
22
	/**
23
	 * Current API route.
24
	 *
25
	 * @since 7.0.0
26
	 *
27
	 * @var bool|string
28
	 */
29
	public $route = false;
30
31
	/**
32
	 * Full API URL endpoint.
33
	 *
34
	 * @since 7.0.0
35
	 *
36
	 * @var bool|string
37
	 */
38
	public $url = false;
39
40
	/**
41
	 * Current API method.
42
	 *
43
	 * @since 7.0.0
44
	 *
45
	 * @var bool|string
46
	 */
47
	public $method = false;
48
49
	/**
50
	 * Is a network request.
51
	 *
52
	 * @since 7.2.0
53
	 *
54
	 * @var bool
55
	 */
56
	public $network = false;
57
58
	/**
59
	 * API token.
60
	 *
61
	 * @since 7.0.0
62
	 *
63
	 * @var bool|string
64
	 */
65
	public $token = false;
66
67
	/**
68
	 * API Key.
69
	 *
70
	 * @since 7.0.0
71
	 *
72
	 * @var bool|string
73
	 */
74
	public $key = false;
75
76
	/**
77
	 * API tt.
78
	 *
79
	 * @since 7.0.0
80
	 *
81
	 * @var bool|string
82
	 */
83
	public $tt = false;
84
85
	/**
86
	 * API return.
87
	 *
88
	 * @since 7.0.0
89
	 *
90
	 * @var bool|string
91
	 */
92
	public $return = false;
93
94
	/**
95
	 * Start date.
96
	 *
97
	 * @since 7.0.0
98
	 *
99
	 * @var string
100
	 */
101
	public $start = '';
102
103
	/**
104
	 * End Date.
105
	 *
106
	 * @since 7.0.0
107
	 *
108
	 * @var string
109
	 */
110
	public $end = '';
111
112
	/**
113
	 * Plugin slug.
114
	 *
115
	 * @since 7.0.0
116
	 *
117
	 * @var bool|string
118
	 */
119
	public $plugin = false;
120
121
	/**
122
	 * URL to test connection with.
123
	 *
124
	 * @since 7.3.2
125
	 *
126
	 * @var string
127
	 */
128
	public $testurl = '';
129
130
	/**
131
	 * Store license.
132
	 */
133
	public $license;
134
135
	/**
136
	 * Store version.
137
	 */
138
	public $miversion;
139
140
	/**
141
	 * Site secret key.
142
	 */
143
	public $sitei;
144
145
	/**
146
	 * Compare end date.
147
	 *
148
	 * @var string
149
	 */
150
	protected $compare_end;
151
152
	/**
153
	 * Compare start date.
154
	 *
155
	 * @var string
156
	 */
157
	protected $compare_start;
158
159
	/**
160
	 * Site URL.
161
	 *
162
	 * @var string
163
	 */
164
	protected $site_url;
165
166
	/**
167
	 * Additional data to add to request body
168
	 *
169
	 * @since 7.0.0
170
	 *
171
	 * @var array
172
	 */
173
	protected $additional_data = array();
174
175
	/**
176
	 * Primary class constructor.
177
	 *
178
	 * @param string $route The API route to target.
179
	 * @param array  $args Array of API credentials.
180
	 * @param string $method The API method.
181
	 *
182
	 * @since 7.0.0
183
	 */
184
	public function __construct( $route, $args, $method = 'POST' ) {
185
186
		// Set class properties.
187
		$this->base     = trailingslashit( monsterinsights_get_api_url() );
188
		$this->route    = $route;
189
		$this->url      = trailingslashit( 'https://' . $this->base . $this->route );
190
		$this->method   = $method;
191
		$this->network  = is_network_admin() || ! empty( $args['network'] );
192
193
		$default_token = $this->network ? MonsterInsights()->auth->get_network_token() : MonsterInsights()->auth->get_token();
0 ignored issues
show
Bug Best Practice introduced by
The property $auth is declared protected in MonsterInsights_Lite. Since you implement __get, consider adding a @property or @property-read.
Loading history...
194
		$default_key   = $this->network ? MonsterInsights()->auth->get_network_key() : MonsterInsights()->auth->get_key();
195
196
		$this->token  = ! empty( $args['token'] ) ? $args['token'] : $default_token;
197
		$this->key    = ! empty( $args['key'] ) ? $args['key'] : $default_key;
198
		$this->tt     = ! empty( $args['tt'] ) ? $args['tt'] : '';
199
		$this->return = ! empty( $args['return'] ) ? $args['return'] : '';
200
		$this->start  = ! empty( $args['start'] ) ? $args['start'] : '';
201
		$this->end    = ! empty( $args['end'] ) ? $args['end'] : '';
202
203
		$this->compare_start = ! empty( $args['compare_start'] ) ? $args['compare_start'] : '';
204
		$this->compare_end   = ! empty( $args['compare_end'] ) ? $args['compare_end'] : '';
205
206
		// We need to do this hack so that the network panel + the site_url of the main site are distinct
207
		$this->site_url = is_network_admin() ? network_admin_url() : home_url();
208
209
		if ( monsterinsights_is_pro_version() ) {
210
			$this->license = $this->network ? MonsterInsights()->license->get_network_license_key() : MonsterInsights()->license->get_site_license_key();
0 ignored issues
show
Bug Best Practice introduced by
The property $license is declared protected in MonsterInsights_Lite. Since you implement __get, consider adding a @property or @property-read.
Loading history...
211
		}
212
		$this->plugin    = MonsterInsights()->plugin_slug;
213
		$this->miversion = MONSTERINSIGHTS_VERSION;
214
		$this->sitei     = ! empty( $args['sitei'] ) ? $args['sitei'] : '';
215
		$this->testurl   = ! empty( $args['testurl'] ) ? $args['testurl'] : '';
216
	}
217
218
	/**
219
	 * Processes the API request.
220
	 *
221
	 * @return mixed $value The response to the API call.
222
	 * @since 7.0.0
223
	 */
224
	public function request( $extra_params = [] ) {
225
		// Make sure we're not blocked
226
		$blocked = $this->is_blocked( $this->url );
227
		if ( $blocked || is_wp_error( $blocked ) ) {
228
			if ( is_wp_error( $blocked ) ) {
229
				// Translators: Placeholder gets replaced with the error message.
230
				return new WP_Error( 'api-error', sprintf( __( 'The firewall of your server is blocking outbound calls. Please contact your hosting provider to fix this issue. %s', 'google-analytics-for-wordpress' ), $blocked->get_error_message() ) );
231
			} else {
232
				return new WP_Error( 'api-error', __( 'The firewall of your server is blocking outbound calls. Please contact your hosting provider to fix this issue.', 'google-analytics-for-wordpress' ) );
233
			}
234
		}
235
236
		// Build the body of the request.
237
		$body = array();
238
239
		if ( ! empty( $this->token ) ) {
240
			$body['token'] = $this->token;
241
		}
242
243
		if ( ! empty( $this->key ) ) {
244
			$body['key'] = $this->key;
245
		}
246
247
		if ( ! empty( $this->tt ) ) {
248
			$body['tt'] = $this->tt;
249
		}
250
251
		if ( ! empty( $this->return ) ) {
252
			$body['return'] = $this->return;
253
		}
254
255
		if ( monsterinsights_is_pro_version() && ! empty( $this->license ) ) {
256
			$body['license'] = $this->license;
257
		}
258
259
		if ( ! empty( $this->start ) ) {
260
			$body['start'] = $this->start;
261
		}
262
263
		if ( ! empty( $this->end ) ) {
264
			$body['end'] = $this->end;
265
		}
266
267
		if ( ! empty( $this->compare_start ) ) {
268
			$body['compare_start'] = $this->compare_start;
269
		}
270
271
		if ( ! empty( $this->compare_end ) ) {
272
			$body['compare_end'] = $this->compare_end;
273
		}
274
275
		if ( ! empty( $this->sitei ) ) {
276
			$body['sitei'] = $this->sitei;
277
		}
278
279
		$body['siteurl']   = $this->site_url;
280
		$body['miversion'] = $this->miversion;
281
282
		// If a plugin API request, add the data.
283
		if ( 'info' == $this->route || 'update' == $this->route ) {
284
			$body['miapi-plugin'] = $this->plugin;
285
		}
286
287
		// Add in additional data if needed.
288
		if ( ! empty( $this->additional_data ) ) {
289
			$body['miapi-data'] = maybe_serialize( $this->additional_data );
290
		}
291
292
		if ( 'GET' == $this->method ) {
293
			$body['time'] = time(); // just to avoid caching
294
		}
295
296
		$body['wp_timezone'] = wp_timezone_string(); // Timezone from WP Settings.
297
298
		$body['timezone'] = date( 'e' );
299
300
		$body['network'] = $this->network ? 'network' : 'site';
301
302
		$body['ip'] = ! empty( $_SERVER['SERVER_ADDR'] ) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_ADDR'])) : '';
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_SERVER['SERVER_ADDR']) can also be of type array; however, parameter $str of sanitize_text_field() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

302
		$body['ip'] = ! empty( $_SERVER['SERVER_ADDR'] ) ? sanitize_text_field(/** @scrutinizer ignore-type */ wp_unslash($_SERVER['SERVER_ADDR'])) : '';
Loading history...
303
304
		// This filter will be removed in the future.
305
		$body = apply_filters( 'monsterinsights_api_request_body', $body );
306
307
        $body = array_merge($body, $extra_params);
308
309
		$string = http_build_query( $body, '', '&' );
310
311
		// Build the headers of the request.
312
		$headers = array(
313
			'Content-Type'  => 'application/x-www-form-urlencoded',
314
			'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0',
315
			'Pragma'        => 'no-cache',
316
			'Expires'       => 0,
317
			'MIAPI-Referer' => is_network_admin() ? network_admin_url() : site_url(),
318
			'MIAPI-Sender'  => 'WordPress',
319
		);
320
321
		// if ( $this->apikey ) {
322
		// $headers['X-MonsterInsights-ApiKey'] = $this->apikey;
323
		// }
324
325
		// Setup data to be sent to the API.
326
		$data = array(
327
			'headers'    => $headers,
328
			'body'       => $body,
329
			'timeout'    => 3000,
330
			'user-agent' => 'MI/' . MONSTERINSIGHTS_VERSION . '; ' . $this->site_url,
331
			'sslverify'  => false,
332
		);
333
334
		// Perform the query and retrieve the response.
335
		$response = 'GET' == $this->method ? wp_remote_get( esc_url_raw( $this->url ) . '?' . $string, $data ) : wp_remote_post( esc_url_raw( $this->url ), $data );
336
337
		// return new WP_Error( 'debug', '<pre>' . var_export( $response, true ) . '</pre>' );
338
339
		if ( is_wp_error( $response ) ) {
340
			return $response;
341
		}
342
343
		$response_code = wp_remote_retrieve_response_code( $response );
344
		$response_body = json_decode( wp_remote_retrieve_body( $response ), true );
345
		// return new WP_Error( 'debug', '<pre>' . var_export( $response_body, true ) . '</pre>' );
346
		// var_dump( $response_body );
347
		// Bail out early if there are any errors.
348
		if ( is_wp_error( $response_body ) ) {
349
			return $response_body;
350
		}
351
352
		// If not a 200 status header, send back error.
353
		if ( 200 != $response_code && 204 != $response_code) {
354
			$type = ! empty( $response_body['type'] ) ? $response_body['type'] : 'api-error';
355
356
			if ( empty( $response_code ) ) {
357
				// Translators: Support link tag starts with url and Support link tag ends.
358
				$message = sprintf(
359
					esc_html__( 'Oops! We encountered an error. Please wait a few minutes and try again. If the issue persists, please %1$scontact our support%2$s team.', 'google-analytics-for-wordpress' ),
360
					'<a target="_blank" href="' . monsterinsights_get_url( 'notice', 'unknown-api-error', 'https://www.monsterinsights.com/my-account/support/' ) . '">',
361
					'</a>'
362
				);
363
364
				return new WP_Error( $type, $message );
365
			}
366
367
			if ( empty( $response_body ) || ( empty( $response_body['message'] ) && empty( $response_body['error'] ) ) ) {
368
				// Translators: Support link tag starts with url, Support link tag ends and placeholder adds the response code.
369
				$message = sprintf(
370
					esc_html__( 'Oops! We ran into a problem. Please try again in a few minutes. If the issue persists please %1$scontact our support%2$s team. Error: API returned a %3$s%4$s%5$s response.', 'google-analytics-for-wordpress' ),
371
					'<a target="_blank" href="' . monsterinsights_get_url( 'notice', 'unknown-api-error', 'https://www.monsterinsights.com/my-account/support/' ) . '">',
372
					'</a>',
373
					'<strong>',
374
					$response_code,
375
					'</strong>'
376
				);
377
378
				return new WP_Error( $type, $message );
379
			}
380
381
			if ( ! empty( $response_body['message'] ) ) {
382
				// Translators: Support link tag starts with url, Support link tag ends, placeholder adds the response code and response message.
383
				$message = sprintf(
384
					esc_html__( 'Oops! We ran into a problem. Please try again in a few minutes. If the issue persists please %1$scontact our support%2$s team. Error: API returned a %3$s%4$d: %5$s%6$s', 'google-analytics-for-wordpress' ),
385
					'<a target="_blank" href="' . monsterinsights_get_url( 'notice', 'unknown-api-error', 'https://www.monsterinsights.com/my-account/support/' ) . '">',
386
					'</a>',
387
					'<strong>',
388
					$response_code,
389
					stripslashes( $response_body['message'] ),
390
					'</strong>'
391
				);
392
393
				return new WP_Error( $type, $message );
394
			}
395
396
			if ( ! empty( $response_body['error'] ) ) {
397
				// Translators: Support link tag starts with url, Support link tag ends, placeholder adds the response code and response message.
398
				$message = sprintf(
399
					esc_html__( 'Oops! We ran into a problem. Please try again in a few minutes. If the issue persists please %1$scontact our support%2$s team. Error: API returned a %3$s%4$d: %5$s%6$s', 'google-analytics-for-wordpress' ),
400
					'<a target="_blank" href="' . monsterinsights_get_url( 'notice', 'unknown-api-error', 'https://www.monsterinsights.com/my-account/support/' ) . '">',
401
					'</a>',
402
					'<strong>',
403
					$response_code,
404
					stripslashes( $response_body['error'] ),
405
					'</strong>'
406
				);
407
408
				return new WP_Error( $type, $message );
409
			}
410
		}
411
412
		// If TT required
413
		if ( ! empty( $this->tt ) ) {
414
			if ( empty( $response_body['tt'] ) || ! hash_equals( $this->tt, $response_body['tt'] ) ) {
0 ignored issues
show
Bug introduced by
It seems like $this->tt can also be of type boolean; however, parameter $known_string of hash_equals() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

414
			if ( empty( $response_body['tt'] ) || ! hash_equals( /** @scrutinizer ignore-type */ $this->tt, $response_body['tt'] ) ) {
Loading history...
415
				// TT isn't set on return or doesn't match
416
				// Translators: Support link tag starts with url and Support link tag ends.
417
				$message = sprintf(
418
					esc_html__( 'Oops! We ran into a problem. Please try again in a few minutes. If the issue persists please %1$scontact our support%2$s team. Error: Improper API Request.', 'google-analytics-for-wordpress' ),
419
					'<a target="_blank" href="' . monsterinsights_get_url( 'notice', 'cannot-verify-license', 'https://www.monsterinsights.com/my-account/support/' ) . '">',
420
					'</a>'
421
				);
422
423
				return new WP_Error( 'validation-error', $message );
424
			}
425
		}
426
427
		// Return the json decoded content.
428
		return $response_body;
429
	}
430
431
	/**
432
	 * Sets a class property.
433
	 *
434
	 * @param string $key The property to set.
435
	 * @param string $val The value to set for the property.
436
	 *
437
	 * @return mixed $value The response to the API call.
438
	 * @since 7.0.0
439
	 */
440
	public function set( $key, $val ) {
441
		$this->{$key} = $val;
442
	}
443
444
	/**
445
	 * Allow additional data to be passed in the request
446
	 *
447
	 * @param array $data
448
	 * return void
449
	 *
450
	 * @since 7.0.0
451
	 */
452
	public function set_additional_data( array $data ) {
453
		$this->additional_data = array_merge( $this->additional_data, $data );
454
	}
455
456
	/**
457
	 * Checks for SSL for making API requests.
458
	 *
459
	 * @since 7.0.0
460
	 *
461
	 * return bool True if SSL is enabled, false otherwise.
462
	 */
463
	public function is_ssl() {
464
		// Use the base is_ssl check first.
465
		if ( is_ssl() ) {
466
			return true;
467
		} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
468
			// Also catch proxies and load balancers.
469
			return true;
470
		} elseif ( defined( 'FORCE_SSL_ADMIN' ) && FORCE_SSL_ADMIN ) {
471
			return true;
472
		}
473
474
		// Otherwise, return false.
475
		return false;
476
	}
477
478
	private function is_blocked( $url = '' ) {
479
		global $Airplane_Mode_Core;
480
481
		if ( defined( 'AIRMDE_VER' ) && ! empty( $Airplane_Mode_Core ) && $Airplane_Mode_Core->enabled() ) {
482
			return new WP_Error( 'api-error', __( 'Oops! The API was unreachable because the plugin, Airplane Mode is active. Please disable and try again.', 'google-analytics-for-wordpress' ) );
483
		}
484
485
		// The below page is a testing empty content HTML page used for firewall/router login detection
486
		// and for image linking purposes in Google Images. We use it to test outbound connections since it is run on google.com
487
		// and is only a few bytes large. Plus on Google's main CDN so it loads in most places in 0.07 seconds or less. Perfect for our
488
		// use case of quickly testing outbound connections.
489
		$testurl = ! empty( $this->testurl ) ? $this->testurl : 'https://www.google.com/blank.html';
490
		if ( defined( 'WP_HTTP_BLOCK_EXTERNAL' ) && WP_HTTP_BLOCK_EXTERNAL ) {
0 ignored issues
show
Bug introduced by
The constant WP_HTTP_BLOCK_EXTERNAL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
491
			if ( defined( 'WP_ACCESSIBLE_HOSTS' ) ) {
492
				$wp_http      = new WP_Http();
493
				$on_blacklist = $wp_http->block_request( $url );
494
				if ( $on_blacklist ) {
495
					return new WP_Error( 'api-error', __( 'Reason: The API was unreachable because the API url is on the WP HTTP blocklist.', 'google-analytics-for-wordpress' ) );
496
				} else {
497
					$params   = array(
498
						'sslverify'  => false,
499
						'timeout'    => 2,
500
						'user-agent' => 'MonsterInsights/' . MONSTERINSIGHTS_VERSION,
501
						'body'       => '',
502
					);
503
					$response = wp_remote_get( $testurl, $params );
504
					if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 ) {
505
						return false;
506
					} else {
507
						if ( is_wp_error( $response ) ) {
508
							return $response;
509
						} else {
510
							return new WP_Error( 'api-error', __( 'Reason: The API was unreachable because the call to Google failed.', 'google-analytics-for-wordpress' ) );
511
						}
512
					}
513
				}
514
			} else {
515
				return new WP_Error( 'api-error', __( 'Reason: The API was unreachable because no external hosts are allowed on this site.', 'google-analytics-for-wordpress' ) );
516
			}
517
		} else {
518
			$params   = array(
519
				'sslverify'  => false,
520
				'timeout'    => 2,
521
				'user-agent' => 'MonsterInsights/' . MONSTERINSIGHTS_VERSION,
522
				'body'       => '',
523
			);
524
			$response = wp_remote_get( $testurl, $params );
525
526
			if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 ) {
527
				return false;
528
			} else {
529
				if ( is_wp_error( $response ) ) {
530
					return $response;
531
				} else {
532
					return new WP_Error( 'api-error', __( 'Reason: The API was unreachable because the call to Google failed.', 'google-analytics-for-wordpress' ) );
533
				}
534
			}
535
		}
536
	}
537
}
538