Completed
Push — add/gdpr-ads-compliance ( 47ea51...6d1e7f )
by
unknown
09:08
created

Jetpack_Geolocation::get_ip_address()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 0
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * Geolocation class
4
 *
5
 * Handles geolocation and updating the geolocation database.
6
 *
7
 * This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com.
8
 */
9
10
/**
11
 * Jetpack_Geolocation Class.
12
 */
13
class Jetpack_Geolocation {
14
15
	/**
16
	 * API endpoints for looking up user IP address.
17
	 *
18
	 * @var array
19
	 */
20
	private static $ip_lookup_apis = array(
21
		'icanhazip'         => 'http://icanhazip.com',
22
		'ipify'             => 'http://api.ipify.org/',
23
		'ipecho'            => 'http://ipecho.net/plain',
24
		'ident'             => 'http://ident.me',
25
		'whatismyipaddress' => 'http://bot.whatismyipaddress.com',
26
	);
27
28
	/**
29
	 * API endpoints for geolocating an IP address
30
	 *
31
	 * @var array
32
	 */
33
	private static $geoip_apis = array(
34
		'ipinfo.io'  => 'https://ipinfo.io/%s/json',
35
		'ip-api.com' => 'http://ip-api.com/json/%s',
36
	);
37
38
	/**
39
	 * Check if server supports MaxMind GeoLite2 Reader.
40
	 *
41
	 * @return bool
42
	 */
43
	private static function supports_geolite2() {
44
		return version_compare( PHP_VERSION, '5.4.0', '>=' );
45
	}
46
47
	/**
48
	 * Get current user IP Address.
49
	 *
50
	 * @return string
51
	 */
52
	public static function get_ip_address() {
53
		if ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) { // WPCS: input var ok, CSRF ok.
54
			return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_REAL_IP'] ) );  // WPCS: input var ok, CSRF ok.
55
		} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { // WPCS: input var ok, CSRF ok.
56
			// Proxy servers can send through this header like this: X-Forwarded-For: client1, proxy1, proxy2
57
			// Make sure we always only send through the first IP in the list which should always be the client IP.
58
			return (string) rest_is_ip_address( trim( current( preg_split( '/[,:]/', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) ) ) ); // WPCS: input var ok, CSRF ok.
59
		} elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) { // @codingStandardsIgnoreLine
60
			return sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ); // @codingStandardsIgnoreLine
61
		}
62
		return '';
63
	}
64
65
	/**
66
	 * Get user IP Address using an external service.
67
	 * This is used mainly as a fallback for users on localhost where
68
	 * get_ip_address() will be a local IP and non-geolocatable.
69
	 *
70
	 * @return string
71
	 */
72
	public static function get_external_ip_address() {
73
		$external_ip_address = '0.0.0.0';
74
75
		if ( '' !== self::get_ip_address() ) {
76
			$transient_name      = 'external_ip_address_' . self::get_ip_address();
77
			$external_ip_address = get_transient( $transient_name );
78
		}
79
80
		if ( false === $external_ip_address ) {
81
			$external_ip_address     = '0.0.0.0';
82
			$ip_lookup_services      = apply_filters( 'jetpack_geolocation_ip_lookup_apis', self::$ip_lookup_apis );
83
			$ip_lookup_services_keys = array_keys( $ip_lookup_services );
84
			shuffle( $ip_lookup_services_keys );
85
86
			foreach ( $ip_lookup_services_keys as $service_name ) {
87
				$service_endpoint = $ip_lookup_services[ $service_name ];
88
				$response         = wp_safe_remote_get( $service_endpoint, array( 'timeout' => 2 ) );
89
90
				if ( ! is_wp_error( $response ) && rest_is_ip_address( $response['body'] ) ) {
91
					$external_ip_address = apply_filters( 'jetpack_geolocation_ip_lookup_api_response', $response['body'], $service_name );
92
					break;
93
				}
94
			}
95
96
			set_transient( $transient_name, $external_ip_address, WEEK_IN_SECONDS );
0 ignored issues
show
Bug introduced by
The variable $transient_name does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
97
		}
98
99
		return $external_ip_address;
100
	}
101
102
	/**
103
	 * Geolocate an IP address.
104
	 *
105
	 * @param  string $ip_address   IP Address.
106
	 * @param  bool   $fallback     If true, fallbacks to alternative IP detection (can be slower).
107
	 * @param  bool   $api_fallback If true, uses geolocation APIs if the database file doesn't exist (can be slower).
108
	 * @return array
109
	 */
110
	public static function geolocate_ip( $ip_address = '', $fallback = true, $api_fallback = true ) {
111
		// Filter to allow custom geolocation of the IP address.
112
		$country_code = apply_filters( 'jetpack_geolocate_ip', false, $ip_address, $fallback, $api_fallback );
113
114
		if ( false === $country_code ) {
115
			// If GEOIP is enabled in CloudFlare, we can use that (Settings -> CloudFlare Settings -> Settings Overview).
116
			if ( ! empty( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) { // WPCS: input var ok, CSRF ok.
117
				$country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) ); // WPCS: input var ok, CSRF ok.
118
			} elseif ( ! empty( $_SERVER['GEOIP_COUNTRY_CODE'] ) ) { // WPCS: input var ok, CSRF ok.
119
				// WP.com VIP has a variable available.
120
				$country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['GEOIP_COUNTRY_CODE'] ) ) ); // WPCS: input var ok, CSRF ok.
121
			} elseif ( ! empty( $_SERVER['HTTP_X_COUNTRY_CODE'] ) ) { // WPCS: input var ok, CSRF ok.
122
				// VIP Go has a variable available also.
123
				$country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_COUNTRY_CODE'] ) ) ); // WPCS: input var ok, CSRF ok.
124
			} else {
125
				$ip_address = $ip_address ? $ip_address : self::get_ip_address();
126
				$database   = apply_filters( 'jetpack_geolocation_local_database_path', JETPACK__PLUGIN_DIR . '_inc/lib/geolite2/GeoLite2-Country.mmdb' );
127
128
				if ( self::supports_geolite2() && file_exists( $database ) ) {
129
					$country_code = self::geolocate_via_db( $ip_address, $database );
130
				} elseif ( $api_fallback ) {
131
					$country_code = self::geolocate_via_api( $ip_address );
132
				} else {
133
					$country_code = '';
134
				}
135
136
				if ( ! $country_code && $fallback ) {
137
					// May be a local environment - find external IP.
138
					return self::geolocate_ip( self::get_external_ip_address(), false, $api_fallback );
139
				}
140
			}
141
		}
142
143
		return $country_code;
144
	}
145
146
	/**
147
	 * Use MAXMIND GeoLite database to geolocation the user.
148
	 *
149
	 * @param  string $ip_address IP address.
150
	 * @param  string $database   Database path.
151
	 * @return string
152
	 */
153
	private static function geolocate_via_db( $ip_address, $database ) {
154
		if ( ! class_exists( 'Jetpack_Geolite_Integration', false ) ) {
155
			require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.jetpack-geolite-integration.php';
156
		}
157
158
		$geolite = new Jetpack_Geolite_Integration( $database );
159
160
		return $geolite->get_country_iso( $ip_address );
161
	}
162
163
	/**
164
	 * Use APIs to Geolocate the user.
165
	 *
166
	 * @param  string $ip_address IP address.
167
	 * @return string|bool
168
	 */
169
	private static function geolocate_via_api( $ip_address ) {
170
		$country_code = get_transient( 'geoip_' . $ip_address );
171
172
		if ( false === $country_code ) {
173
			$geoip_services      = apply_filters( 'jetpack_geolocation_geoip_apis', self::$geoip_apis );
174
			$geoip_services_keys = array_keys( $geoip_services );
175
			shuffle( $geoip_services_keys );
176
177
			foreach ( $geoip_services_keys as $service_name ) {
178
				$service_endpoint = $geoip_services[ $service_name ];
179
				$response         = wp_safe_remote_get( sprintf( $service_endpoint, $ip_address ), array( 'timeout' => 2 ) );
180
181
				if ( ! is_wp_error( $response ) && $response['body'] ) {
182
					switch ( $service_name ) {
183
						case 'ipinfo.io':
184
							$data         = json_decode( $response['body'] );
185
							$country_code = isset( $data->country ) ? $data->country : '';
186
							break;
187
						case 'ip-api.com':
188
							$data         = json_decode( $response['body'] );
189
							$country_code = isset( $data->countryCode ) ? $data->countryCode : ''; // @codingStandardsIgnoreLine
190
							break;
191
						default:
192
							$country_code = apply_filters( 'jetpack_geolocation_geoip_response_' . $service_name, '', $response['body'] );
193
							break;
194
					}
195
196
					$country_code = sanitize_text_field( strtoupper( $country_code ) );
197
198
					if ( $country_code ) {
199
						break;
200
					}
201
				}
202
			}
203
204
			set_transient( 'geoip_' . $ip_address, $country_code, WEEK_IN_SECONDS );
205
		}
206
207
		return $country_code;
208
	}
209
}
210