forked from yellowtree/geoip-detect
-
Notifications
You must be signed in to change notification settings - Fork 0
/
geoip-detect-lib.php
561 lines (467 loc) · 16.2 KB
/
geoip-detect-lib.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
<?php
/*
Copyright 2013-2021 Yellow Tree, Siegen, Germany
Author: Benjamin Pick (wp-geoip-detect| |posteo.de)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
// This file contains function that are necessary for the plugin, but not deemed as API.
// Their name / parameter may change without warning.
use YellowTree\GeoipDetect\DataSources\DataSourceRegistry;
// This file is outside composer root in order to not distribute all the other symfony files
require_once(__DIR__ . '/lib/vendor/symfony/http-foundation/IpUtils.php');
use Symfony\Component\HttpFoundation\IpUtils;
/**
* Take the parameter options and add the default values.
* @param array $options
* return $options
*/
function _geoip_detect2_process_options($options) {
// For backwards compat 2.4.0-2.5.0
if (is_bool($options)) {
_doing_it_wrong('Geolocation IP Detection Plugin: geoip_detect2_get_info_from_ip()', '$skipCache has been renamed to $options. Instead of TRUE, now use "[\'skipCache\' => TRUE]".', '2.5.0');
$value = $options;
$options = array();
$options['skipCache'] = $value;
}
// Check if source exists
if (isset($options['source'])) {
$registry = DataSourceRegistry::getInstance();
if (!$registry->sourceExists($options['source']))
unset($options['source']);
}
/**
* Filter: geoip_detect2_options
* You can programmatically change the defaults etc.
*
* @param array $options The options array
*/
$options = apply_filters('geoip_detect2_options', $options);
$defaultOptions = array(
'skipCache' => false,
'source' => get_option('geoip-detect-source', DataSourceRegistry::DEFAULT_SOURCE),
);
$options = $options + $defaultOptions;
return $options;
}
/*
* Get the Maxmind Reader
* (Use this if you want to use other methods than "city" or otherwise customize behavior.)
*
* @param array(string) List of locale codes to use in name property
* from most preferred to least preferred. (Default: Site language, en)
* @param boolean If locale filter should be skipped (default: No)
* @return GeoIp2\Database\Reader The reader, ready to do its work. Don't forget to `close()` it afterwards. NULL if file not found (or other problems).
* NULL if initialization went wrong (e.g., File not found.)
*/
function _geoip_detect2_get_reader($locales = null, $skipLocaleFilter = false, &$sourceId = '', $options = array()) {
if (! $skipLocaleFilter) {
/**
* Filter: geoip_detect2_locales
*
* @param array(string) $locales
* Current locales.
*/
$locales = apply_filters ( 'geoip_detect2_locales', $locales );
}
$reader = null;
$source = DataSourceRegistry::getInstance()->getSource($options['source']);
if ($source) {
$reader = $source->getReader($locales, $options);
$sourceId = $source->getId();
}
/**
* Filter: geoip_detect2_reader
* You can customize your reader here.
* This filter will be called for every IP request.
*
* @param
* GeoIp2\Database\ProviderInterface Reader (by default: GeoLite City)
* @param
* array(string) Locale precedence
*/
$reader = apply_filters('geoip_detect2_reader', $reader, $locales );
return $reader;
}
function _ip_to_s($ip) : string {
$binary = '';
try {
$binary = @inet_pton($ip);
} catch (\Throwable $e) { }
if (empty($binary))
return '';
return base64_encode($binary);
}
function _geoip_detect2_get_data_from_cache($ip, $source) {
if (!DataSourceRegistry::getInstance()->isSourceCachable($source)) {
return null;
}
$ip_s = _ip_to_s($ip);
if (!$ip_s) {
return null;
}
$data = get_transient('geoip_detect_c_' . $source . '_' . $ip_s);
return $data;
}
function _geoip_detect2_add_data_to_cache($data, $ip) {
$source = $data['extra']['source'];
if (!DataSourceRegistry::getInstance()->isSourceCachable($source)) {
return null;
}
if (GEOIP_DETECT_READER_CACHE_TIME === 0) {
// Caching is disabled
return null;
}
$data['extra']['cached'] = time();
unset($data['maxmind']['queries_remaining']);
$ip_s = _ip_to_s($ip);
// Do not cache invalid IPs
if (!$ip_s) {
return;
}
// Do not cache error lookups (they might be temporary)
if (!empty($data['extra']['error'])) {
return;
}
set_transient('geoip_detect_c_' . $source . '_' . $ip_s, $data, GEOIP_DETECT_READER_CACHE_TIME);
}
function _geoip_detect2_empty_cache() {
// This does not work for memcache. But it doesn't hurt either
// ToDo expose to UI if Source is cacheable
global $wpdb;
$wpdb->query( "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE ('_transient_geoip_detect_c_%')" );
}
/**
* @return \GeoIp2\Model\Country
*/
function _geoip_detect2_get_record_from_reader($reader, $ip, &$error) {
$record = null;
$ip = trim($ip);
if ($reader) {
// When plugin installed on development boxes:
// If the client IP is not a public IP, use the public IP of the server instead.
// Of course this only works if the internet can be accessed.
if ($ip == 'me' || geoip_detect_is_internal_ip($ip)) {
$ip = geoip_detect2_get_external_ip_adress();
}
try {
try {
$record = $reader->city($ip);
} catch (\BadMethodCallException $e) {
$record = $reader->country($ip);
}
} catch(\Exception $e) {
$error = 'Lookup Error: ' . $e->getMessage();
}
$reader->close();
} else {
$error = 'No reader was found. Check if the configuration is complete and correct.';
}
if (is_null($record)) {
return _geoip_detect2_get_new_empty_record();
}
return $record;
}
function _geoip_detect2_get_new_empty_record($ip = '') {
$data = array('traits' => array('ip_address' => $ip), 'is_empty' => true);
return new \GeoIp2\Model\City($data);
}
function _geoip_detect2_record_enrich_data($record, $ip, $sourceId, $error) : array {
if (is_object($record) && method_exists($record, 'jsonSerialize')) {
$data = $record->jsonSerialize();
} else {
$data = array('traits' => array('ip_address' => $ip), 'is_empty' => true);
}
if (!isset($data['is_empty'])) {
$data['is_empty'] = false;
}
if (empty($data['traits']['ip_address'])) {
$data['traits']['ip_address'] = $ip;
}
$data['extra']['source'] = $sourceId;
$data['extra']['cached'] = 0;
if ($error || !isset($data['extra']['error'])) {
$data['extra']['error'] = $error;
}
/**
* Filter: geoip_detect2_record_data
* After loading the information from the Geolocation database, you can add information to it.
*
* @param array $data Information found.
* @param string $orig_ip IP that originally passed to the function.
* @return array
*/
$data = apply_filters('geoip_detect2_record_data', $data, $ip);
return $data;
}
/**
* GeoIPv2 doesn't always include a timezone when v1 did.
* Region ids have changed, so countries with several time zones are out of luck.
*
* @param array $record
*/
function _geoip_detect2_try_to_fix_timezone($data) {
if (!empty($data['location']['time_zone']))
return $data;
if (!function_exists('_geoip_detect_get_time_zone')) {
require_once(GEOIP_PLUGIN_DIR . '/lib/timezone.php');
}
if (!empty($data['country']['iso_code'])) {
$data['location']['time_zone'] = _geoip_detect_get_time_zone($data['country']['iso_code'], isset($data['subdivisions'][0]['iso_code']) ? $data['subdivisions'][0]['iso_code'] : null);
} else {
unset($data['location']['time_zone']);
}
return $data;
}
add_filter('geoip_detect2_record_data', '_geoip_detect2_try_to_fix_timezone');
/**
* Add country name, if not known yet
*/
function _geoip_detect2_add_geonames_data($data) {
static $countryInfo = null;
if (is_null($countryInfo))
$countryInfo = new \YellowTree\GeoipDetect\Geonames\CountryInformation;
if (!empty($data['country']['iso_code'])) {
$geonamesData = $countryInfo->getInformationAboutCountry($data['country']['iso_code']);
$data = array_replace_recursive($geonamesData, $data);
if (!empty($geonamesData['country']['iso_code3'])) {
$data['extra']['country_iso_code3'] = $geonamesData['country']['iso_code3'];
}
$emoji = $countryInfo->getFlagEmoji($data['country']['iso_code']);
if ($emoji && empty($data['extra']['flag'])) {
$data['extra']['flag'] = $emoji;
}
$tel = $countryInfo->getTelephonePrefix($data['country']['iso_code']);
if ($tel && empty($data['extra']['tel'])) {
$data['extra']['tel'] = $tel;
}
}
return $data;
}
add_filter('geoip_detect2_record_data', '_geoip_detect2_add_geonames_data');
/**
* IPv6-Adresses can be written in different formats. Make sure they are standardized.
* For IPv4-Adresses, spaces are removed.
*/
function geoip_detect_normalize_ip(string $ip) : string {
$ip = trim($ip);
$binary = '';
try {
$binary = @inet_pton($ip);
} catch (\Throwable $e) { }
if (empty($binary))
return $ip; // Probably an IPv6 adress & IPv6 is not supported. Or not a valid IP.
$ip = inet_ntop($binary);
return $ip;
}
function geoip_detect_sanitize_ip_list(string $ip_list) : string {
$list = explode(',', $ip_list);
$ret = array();
foreach ($list as $ip) {
$ip = trim($ip);
$parts = explode('/', $ip, 2);
if (isset($parts[1])) {
if (!is_numeric($parts[1])) {
unset($parts[1]);
}
}
if (!geoip_detect_is_ip($parts[0])) {
continue;
}
$ip = $parts[0] . (isset($parts[1]) ? ('/' . (int) $parts[1]) : '');
$ret[] = $ip;
}
return implode(', ', $ret);
}
/**
* Check if the expected IP left matches the actual IP
* @param string $actual IP
* @param string|array $expected IP (can include subnet)
* @return boolean
*/
function geoip_detect_is_ip_equal(string $actual, $expected) : bool {
try {
return IpUtils::checkIp($actual, $expected);
} catch(\Exception $e) {
// IPv6 not supported by PHP
// Do string comparison instead (very rough: no subnet, no IP noramlization)
if (is_array($expected)) {
return in_array($actual, $expected, true);
} else {
return $actual === $expected;
}
}
}
function geoip_detect_is_ip(string $ip, bool $noIpv6 = false) : bool {
$flags = FILTER_FLAG_IPV4;
if (GEOIP_DETECT_IPV6_SUPPORTED && !$noIpv6)
$flags = $flags | FILTER_FLAG_IPV6;
return filter_var($ip, FILTER_VALIDATE_IP, $flags) !== false;
}
function geoip_detect_is_ip_in_range(string $ip, string $range_start, string $range_end) : bool {
$long_ip = ip2long($ip);
if ($long_ip === false) // Not IPv4
return false;
if($long_ip >= ip2long($range_start) && $long_ip <= ip2long($range_end))
return true;
return false;
}
/**
* Check if IP is not in RFC private IP range
* (for local development)
* @param string $ip IP (IPv4 or IPv6)
* @return boolean TRUE if private
*/
function geoip_detect_is_public_ip(string $ip) : bool {
// filver_var only detects 127.0.0.1 as local ...
if (geoip_detect_is_ip_equal($ip, '127.0.0.0/8'))
return false;
if (trim($ip) === '0.0.0.0')
return false;
$flags = FILTER_FLAG_IPV4 // IP can be v4 or v6
| FILTER_FLAG_NO_PRIV_RANGE // It may not be in the RFC private range
| FILTER_FLAG_NO_RES_RANGE; // It may not be in the RFC reserved range
if (GEOIP_DETECT_IPV6_SUPPORTED)
$flags = $flags | FILTER_FLAG_IPV6;
$is_public = filter_var($ip, FILTER_VALIDATE_IP, $flags) !== false;
return $is_public;
}
function geoip_detect_is_internal_ip(string $ip) : bool {
return geoip_detect_is_ip($ip) && !geoip_detect_is_public_ip($ip);
}
function _geoip_detect2_get_external_ip_services(int $nb = 3, bool $needsCORS = false) : array {
$ipservicesThatAllowCORS = array(
'http://ipv4.icanhazip.com',
'http://v4.ident.me',
);
$ipservicesWithoutCORS = array(
'http://ipecho.net/plain',
'http://bot.whatismyipaddress.com',
);
$ipservices = $ipservicesThatAllowCORS;
if (!$needsCORS) {
$ipservices = array_merge($ipservices, $ipservicesWithoutCORS);
}
// Randomizing to avoid querying the same service each time
shuffle($ipservices);
$ipservices = apply_filters('geiop_detect_ipservices', $ipservices);
$ipservices = array_slice($ipservices, 0, $nb);
return $ipservices;
}
/**
* Sometimes we can only see an local IP adress (local development environment.)
* In this case we need to ask an internet server which IP adress our internet connection has.
* (This function is not cached. Some providers may throttle our requests, that's why caching is enabled by default.)
*
* @return string The detected IPv4 Adress. If none is found, '0.0.0.0' is returned instead.
*/
function _geoip_detect_get_external_ip_adress_without_cache() : string
{
$ipservices = _geoip_detect2_get_external_ip_services();
foreach ($ipservices as $url)
{
$ret = wp_remote_get($url, array('timeout' => defined('WP_TESTS_TITLE') ? 3 : 1.5));
if (is_wp_error($ret)) {
if (WP_DEBUG || defined('WP_TESTS_TITLE')) {
trigger_error('_geoip_detect_get_external_ip_adress_without_cache(): Curl error (' . $url . '): ' . $ret->get_error_message(), E_USER_NOTICE);
}
} else if (isset($ret['response']['code']) && $ret['response']['code'] != 200) {
if (WP_DEBUG || defined('WP_TESTS_TITLE')) {
trigger_error('_geoip_detect_get_external_ip_adress_without_cache(): HTTP error (' . $url . '): Returned code ' . $ret['response']['code'], E_USER_NOTICE);
}
} else {
if (isset($ret['body'])) {
$ip = trim($ret['body']);
if (geoip_detect_is_ip($ip))
return $ip;
}
if (WP_DEBUG || defined('WP_TESTS_TITLE')) {
trigger_error('_geoip_detect_get_external_ip_adress_without_cache(): HTTP error (' . $url . '): Did not return an IP: ' . $ret['body'], E_USER_NOTICE);
}
}
}
return '0.0.0.0';
}
// @see https://stackoverflow.com/questions/2637945/getting-relative-path-from-absolute-path-in-php
function geoip_detect_get_relative_path($from, $to)
{
// some compatibility fixes for Windows paths
$from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
$to = is_dir($to) ? rtrim($to, '\/') . '/' : $to;
$from = str_replace('\\', '/', $from);
$to = str_replace('\\', '/', $to);
$from = explode('/', $from);
$to = explode('/', $to);
$relPath = $to;
foreach($from as $depth => $dir) {
// find first non-matching dir
if($dir === $to[$depth]) {
// ignore this directory
array_shift($relPath);
} else {
// get number of remaining dirs to $from
$remaining = count($from) - $depth;
if($remaining > 1) {
// add traversals up to first matching dir
$padLength = (count($relPath) + $remaining - 1) * -1;
$relPath = array_pad($relPath, $padLength, '..');
break;
} else {
$relPath[0] = $relPath[0];
}
}
}
return implode('/', $relPath);
}
function _geoip_maybe_disable_pagecache() : bool {
if (!get_option('geoip-detect-disable_pagecache'))
return false;
// WP Super Cache, W3 Total Cache
if (!defined('DONOTCACHEPAGE'))
define('DONOTCACHEPAGE', true);
if (!defined('DONOTCACHEOBJECT'))
define('DONOTCACHEOBJECT', true);
if (!defined('DONOTCACHEDB'))
define('DONOTCACHEDB', true);
if (!headers_sent()) {
header('Cache-Control: private, proxy-revalidate, s-maxage=0');
}
return true;
}
function _geoip_dashes_to_camel_case(string $string, bool $capitalizeFirstCharacter = false) : string {
$str = str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
if (!$capitalizeFirstCharacter) {
$str = lcfirst($str);
}
return $str;
}
function geoip_detect_format_localtime($timestamp = -1) : string {
if ($timestamp === -1) {
$timestamp = time();
}
if ($timestamp == 0) {
return __('Never', 'geoip-detect');
}
$format = get_option('date_format') . ' '. get_option('time_format');
return get_date_from_gmt ( date( 'Y-m-d H:i:s', $timestamp ), $format);
}
function _geoip_str_begins_with($string, $startString) : bool
{
$len = mb_strlen($startString);
return (mb_substr($string, 0, $len) === $startString);
}
function _geoip_str_ends_with($string, $startString) : bool
{
$len = mb_strlen($startString);
//if ($len === 0) return true; // Not sure what is "expected behavior"
return (mb_substr($string, -$len) === $startString);
}