|
1
|
|
|
<?php |
|
2
|
|
|
/***************************************************************************\ |
|
3
|
|
|
| Sypex Geo version 2.2.3 | |
|
4
|
|
|
| (c)2006-2014 zapimir [email protected] http://sypex.net/ | |
|
5
|
|
|
| (c)2006-2014 BINOVATOR [email protected] | |
|
6
|
|
|
|---------------------------------------------------------------------------| |
|
7
|
|
|
| created: 2006.10.17 18:33 modified: 2014.06.20 18:57 | |
|
8
|
|
|
|---------------------------------------------------------------------------| |
|
9
|
|
|
| Sypex Geo is released under the terms of the BSD license | |
|
10
|
|
|
| http://sypex.net/bsd_license.txt | |
|
11
|
|
|
\***************************************************************************/ |
|
12
|
|
|
namespace Geography; |
|
13
|
|
|
define ('SXGEO_FILE', 0); |
|
14
|
|
|
define ('SXGEO_MEMORY', 1); |
|
15
|
|
|
define ('SXGEO_BATCH', 2); |
|
16
|
|
|
class SxGeo { |
|
17
|
|
|
protected $fh; |
|
18
|
|
|
protected $ip1c; |
|
19
|
|
|
protected $info; |
|
20
|
|
|
protected $range; |
|
21
|
|
|
protected $db_begin; |
|
22
|
|
|
protected $b_idx_str; |
|
23
|
|
|
protected $m_idx_str; |
|
24
|
|
|
protected $b_idx_arr; |
|
25
|
|
|
protected $m_idx_arr; |
|
26
|
|
|
protected $m_idx_len; |
|
27
|
|
|
protected $db_items; |
|
28
|
|
|
protected $country_size; |
|
29
|
|
|
protected $db; |
|
30
|
|
|
protected $regions_db; |
|
31
|
|
|
protected $cities_db; |
|
32
|
|
|
|
|
33
|
|
|
public $id2iso = array( |
|
34
|
|
|
'', 'AP', 'EU', 'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'CW', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', |
|
35
|
|
|
'AW', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BM', 'BN', 'BO', 'BR', 'BS', |
|
36
|
|
|
'BT', 'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', |
|
37
|
|
|
'CO', 'CR', 'CU', 'CV', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', |
|
38
|
|
|
'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'SX', 'GA', 'GB', 'GD', 'GE', 'GF', |
|
39
|
|
|
'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', |
|
40
|
|
|
'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JM', 'JO', 'JP', 'KE', |
|
41
|
|
|
'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', |
|
42
|
|
|
'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', |
|
43
|
|
|
'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', |
|
44
|
|
|
'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', |
|
45
|
|
|
'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', |
|
46
|
|
|
'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SY', 'SZ', 'TC', 'TD', 'TF', |
|
47
|
|
|
'TG', 'TH', 'TJ', 'TK', 'TM', 'TN', 'TO', 'TL', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', |
|
48
|
|
|
'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'RS', 'ZA', |
|
49
|
|
|
'ZM', 'ME', 'ZW', 'A1', 'XK', 'O1', 'AX', 'GG', 'IM', 'JE', 'BL', 'MF', 'BQ', 'SS' |
|
50
|
|
|
); |
|
51
|
|
|
|
|
52
|
|
|
public $batch_mode = false; |
|
53
|
|
|
public $memory_mode = false; |
|
54
|
|
|
|
|
55
|
|
|
public function __construct($db_file = 'SxGeo.dat', $type = SXGEO_FILE){ |
|
56
|
|
|
$this->fh = fopen($db_file, 'rb'); |
|
57
|
|
|
// Сначала убеждаемся, что есть файл базы данных |
|
58
|
|
|
$header = fread($this->fh, 40); // В версии 2.2 заголовок увеличился на 8 байт |
|
59
|
|
|
if(substr($header, 0, 3) != 'SxG') die("Can't open {$db_file}\n"); |
|
|
|
|
|
|
60
|
|
|
$info = unpack('Cver/Ntime/Ctype/Ccharset/Cb_idx_len/nm_idx_len/nrange/Ndb_items/Cid_len/nmax_region/nmax_city/Nregion_size/Ncity_size/nmax_country/Ncountry_size/npack_size', substr($header, 3)); |
|
61
|
|
|
if($info['b_idx_len'] * $info['m_idx_len'] * $info['range'] * $info['db_items'] * $info['time'] * $info['id_len'] == 0) die("Wrong file format {$db_file}\n"); |
|
|
|
|
|
|
62
|
|
|
$this->range = $info['range']; |
|
63
|
|
|
$this->b_idx_len = $info['b_idx_len']; |
|
|
|
|
|
|
64
|
|
|
$this->m_idx_len = $info['m_idx_len']; |
|
65
|
|
|
$this->db_items = $info['db_items']; |
|
66
|
|
|
$this->id_len = $info['id_len']; |
|
|
|
|
|
|
67
|
|
|
$this->block_len = 3 + $this->id_len; |
|
|
|
|
|
|
68
|
|
|
$this->max_region = $info['max_region']; |
|
|
|
|
|
|
69
|
|
|
$this->max_city = $info['max_city']; |
|
|
|
|
|
|
70
|
|
|
$this->max_country = $info['max_country']; |
|
|
|
|
|
|
71
|
|
|
$this->country_size= $info['country_size']; |
|
72
|
|
|
$this->batch_mode = $type & SXGEO_BATCH; |
|
73
|
|
|
$this->memory_mode = $type & SXGEO_MEMORY; |
|
74
|
|
|
$this->pack = $info['pack_size'] ? explode("\0", fread($this->fh, $info['pack_size'])) : ''; |
|
|
|
|
|
|
75
|
|
|
$this->b_idx_str = fread($this->fh, $info['b_idx_len'] * 4); |
|
76
|
|
|
$this->m_idx_str = fread($this->fh, $info['m_idx_len'] * 4); |
|
77
|
|
|
|
|
78
|
|
|
$this->db_begin = ftell($this->fh); |
|
79
|
|
|
if ($this->batch_mode) { |
|
80
|
|
|
$this->b_idx_arr = array_values(unpack("N*", $this->b_idx_str)); // Быстрее в 5 раз, чем с циклом |
|
81
|
|
|
unset ($this->b_idx_str); |
|
82
|
|
|
$this->m_idx_arr = str_split($this->m_idx_str, 4); // Быстрее в 5 раз чем с циклом |
|
83
|
|
|
unset ($this->m_idx_str); |
|
84
|
|
|
} |
|
85
|
|
|
if ($this->memory_mode) { |
|
86
|
|
|
$this->db = fread($this->fh, $this->db_items * $this->block_len); |
|
87
|
|
|
$this->regions_db = $info['region_size'] > 0 ? fread($this->fh, $info['region_size']) : ''; |
|
88
|
|
|
$this->cities_db = $info['city_size'] > 0 ? fread($this->fh, $info['city_size']) : ''; |
|
89
|
|
|
} |
|
90
|
|
|
$this->info = $info; |
|
91
|
|
|
$this->info['regions_begin'] = $this->db_begin + $this->db_items * $this->block_len; |
|
92
|
|
|
$this->info['cities_begin'] = $this->info['regions_begin'] + $info['region_size']; |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
protected function search_idx($ipn, $min, $max){ |
|
96
|
|
|
if($this->batch_mode){ |
|
97
|
|
|
while($max - $min > 8){ |
|
98
|
|
|
$offset = ($min + $max) >> 1; |
|
99
|
|
|
if ($ipn > $this->m_idx_arr[$offset]) $min = $offset; |
|
100
|
|
|
else $max = $offset; |
|
101
|
|
|
} |
|
102
|
|
|
while ($ipn > $this->m_idx_arr[$min] && $min++ < $max){}; |
|
|
|
|
|
|
103
|
|
|
} |
|
104
|
|
|
else { |
|
105
|
|
View Code Duplication |
while($max - $min > 8){ |
|
|
|
|
|
|
106
|
|
|
$offset = ($min + $max) >> 1; |
|
107
|
|
|
if ($ipn > substr($this->m_idx_str, $offset*4, 4)) $min = $offset; |
|
108
|
|
|
else $max = $offset; |
|
109
|
|
|
} |
|
110
|
|
|
while ($ipn > substr($this->m_idx_str, $min*4, 4) && $min++ < $max){}; |
|
|
|
|
|
|
111
|
|
|
} |
|
112
|
|
|
return $min; |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
protected function search_db($str, $ipn, $min, $max){ |
|
116
|
|
|
if($max - $min > 1) { |
|
117
|
|
|
$ipn = substr($ipn, 1); |
|
118
|
|
View Code Duplication |
while($max - $min > 8){ |
|
|
|
|
|
|
119
|
|
|
$offset = ($min + $max) >> 1; |
|
120
|
|
|
if ($ipn > substr($str, $offset * $this->block_len, 3)) $min = $offset; |
|
121
|
|
|
else $max = $offset; |
|
122
|
|
|
} |
|
123
|
|
|
while ($ipn >= substr($str, $min * $this->block_len, 3) && ++$min < $max){}; |
|
|
|
|
|
|
124
|
|
|
} |
|
125
|
|
|
else { |
|
126
|
|
|
$min++; |
|
127
|
|
|
} |
|
128
|
|
|
return hexdec(bin2hex(substr($str, $min * $this->block_len - $this->id_len, $this->id_len))); |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
public function get_num($ip){ |
|
132
|
|
|
$ip1n = (int)$ip; // Первый байт |
|
133
|
|
|
if($ip1n == 0 || $ip1n == 10 || $ip1n == 127 || $ip1n >= $this->b_idx_len || false === ($ipn = ip2long($ip))) return false; |
|
134
|
|
|
$ipn = pack('N', $ipn); |
|
135
|
|
|
$this->ip1c = chr($ip1n); |
|
136
|
|
|
// Находим блок данных в индексе первых байт |
|
137
|
|
|
if ($this->batch_mode){ |
|
138
|
|
|
$blocks = array('min' => $this->b_idx_arr[$ip1n-1], 'max' => $this->b_idx_arr[$ip1n]); |
|
139
|
|
|
} |
|
140
|
|
|
else { |
|
141
|
|
|
$blocks = unpack("Nmin/Nmax", substr($this->b_idx_str, ($ip1n - 1) * 4, 8)); |
|
142
|
|
|
} |
|
143
|
|
|
if ($blocks['max'] - $blocks['min'] > $this->range){ |
|
144
|
|
|
// Ищем блок в основном индексе |
|
145
|
|
|
$part = $this->search_idx($ipn, floor($blocks['min'] / $this->range), floor($blocks['max'] / $this->range)-1); |
|
146
|
|
|
// Нашли номер блока в котором нужно искать IP, теперь находим нужный блок в БД |
|
147
|
|
|
$min = $part > 0 ? $part * $this->range : 0; |
|
148
|
|
|
$max = $part > $this->m_idx_len ? $this->db_items : ($part+1) * $this->range; |
|
149
|
|
|
// Нужно проверить чтобы блок не выходил за пределы блока первого байта |
|
150
|
|
|
if($min < $blocks['min']) $min = $blocks['min']; |
|
151
|
|
|
if($max > $blocks['max']) $max = $blocks['max']; |
|
152
|
|
|
} |
|
153
|
|
|
else { |
|
154
|
|
|
$min = $blocks['min']; |
|
155
|
|
|
$max = $blocks['max']; |
|
156
|
|
|
} |
|
157
|
|
|
$len = $max - $min; |
|
158
|
|
|
// Находим нужный диапазон в БД |
|
159
|
|
|
if ($this->memory_mode) { |
|
160
|
|
|
return $this->search_db($this->db, $ipn, $min, $max); |
|
161
|
|
|
} |
|
162
|
|
|
else { |
|
163
|
|
|
fseek($this->fh, $this->db_begin + $min * $this->block_len); |
|
164
|
|
|
return $this->search_db(fread($this->fh, $len * $this->block_len), $ipn, 0, $len); |
|
165
|
|
|
} |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
protected function readData($seek, $max, $type){ |
|
169
|
|
|
$raw = ''; |
|
170
|
|
|
if($seek && $max) { |
|
171
|
|
|
if ($this->memory_mode) { |
|
172
|
|
|
$raw = substr($type == 1 ? $this->regions_db : $this->cities_db, $seek, $max); |
|
173
|
|
|
} else { |
|
174
|
|
|
fseek($this->fh, $this->info[$type == 1 ? 'regions_begin' : 'cities_begin'] + $seek); |
|
175
|
|
|
$raw = fread($this->fh, $max); |
|
176
|
|
|
} |
|
177
|
|
|
} |
|
178
|
|
|
return $this->unpack($this->pack[$type], $raw); |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
protected function parseCity($seek, $full = false){ |
|
182
|
|
|
if(!$this->pack) return false; |
|
183
|
|
|
$only_country = false; |
|
184
|
|
|
if($seek < $this->country_size){ |
|
185
|
|
|
$country = $this->readData($seek, $this->max_country, 0); |
|
186
|
|
|
$city = $this->unpack($this->pack[2]); |
|
187
|
|
|
$city['lat'] = $country['lat']; |
|
188
|
|
|
$city['lon'] = $country['lon']; |
|
189
|
|
|
$only_country = true; |
|
190
|
|
|
} |
|
191
|
|
|
else { |
|
192
|
|
|
$city = $this->readData($seek, $this->max_city, 2); |
|
193
|
|
|
$country = array('id' => $city['country_id'], 'iso' => $this->id2iso[$city['country_id']]); |
|
194
|
|
|
unset($city['country_id']); |
|
195
|
|
|
} |
|
196
|
|
|
if($full) { |
|
197
|
|
|
$region = $this->readData($city['region_seek'], $this->max_region, 1); |
|
198
|
|
|
if(!$only_country) $country = $this->readData($region['country_seek'], $this->max_country, 0); |
|
199
|
|
|
unset($city['region_seek']); |
|
200
|
|
|
unset($region['country_seek']); |
|
201
|
|
|
return array('city' => $city, 'region' => $region, 'country' => $country); |
|
202
|
|
|
} |
|
203
|
|
|
else { |
|
204
|
|
|
unset($city['region_seek']); |
|
205
|
|
|
return array('city' => $city, 'country' => array('id' => $country['id'], 'iso' => $country['iso'])); |
|
206
|
|
|
} |
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
protected function unpack($pack, $item = ''){ |
|
210
|
|
|
$unpacked = array(); |
|
211
|
|
|
$empty = empty($item); |
|
212
|
|
|
$pack = explode('/', $pack); |
|
213
|
|
|
$pos = 0; |
|
214
|
|
|
foreach($pack AS $p){ |
|
215
|
|
|
list($type, $name) = explode(':', $p); |
|
216
|
|
|
$type0 = $type{0}; |
|
217
|
|
|
if($empty) { |
|
218
|
|
|
$unpacked[$name] = $type0 == 'b' || $type0 == 'c' ? '' : 0; |
|
219
|
|
|
continue; |
|
220
|
|
|
} |
|
221
|
|
|
switch($type0){ |
|
222
|
|
|
case 't': |
|
223
|
|
|
case 'T': $l = 1; break; |
|
|
|
|
|
|
224
|
|
|
case 's': |
|
225
|
|
|
case 'n': |
|
226
|
|
|
case 'S': $l = 2; break; |
|
|
|
|
|
|
227
|
|
|
case 'm': |
|
228
|
|
|
case 'M': $l = 3; break; |
|
|
|
|
|
|
229
|
|
|
case 'd': $l = 8; break; |
|
|
|
|
|
|
230
|
|
|
case 'c': $l = (int)substr($type, 1); break; |
|
|
|
|
|
|
231
|
|
|
case 'b': $l = strpos($item, "\0", $pos)-$pos; break; |
|
|
|
|
|
|
232
|
|
|
default: $l = 4; |
|
|
|
|
|
|
233
|
|
|
} |
|
234
|
|
|
$val = substr($item, $pos, $l); |
|
235
|
|
|
switch($type0){ |
|
236
|
|
|
case 't': $v = unpack('c', $val); break; |
|
|
|
|
|
|
237
|
|
|
case 'T': $v = unpack('C', $val); break; |
|
|
|
|
|
|
238
|
|
|
case 's': $v = unpack('s', $val); break; |
|
|
|
|
|
|
239
|
|
|
case 'S': $v = unpack('S', $val); break; |
|
|
|
|
|
|
240
|
|
|
case 'm': $v = unpack('l', $val . (ord($val{2}) >> 7 ? "\xff" : "\0")); break; |
|
|
|
|
|
|
241
|
|
|
case 'M': $v = unpack('L', $val . "\0"); break; |
|
|
|
|
|
|
242
|
|
|
case 'i': $v = unpack('l', $val); break; |
|
|
|
|
|
|
243
|
|
|
case 'I': $v = unpack('L', $val); break; |
|
|
|
|
|
|
244
|
|
|
case 'f': $v = unpack('f', $val); break; |
|
|
|
|
|
|
245
|
|
|
case 'd': $v = unpack('d', $val); break; |
|
|
|
|
|
|
246
|
|
|
|
|
247
|
|
|
case 'n': $v = current(unpack('s', $val)) / pow(10, $type{1}); break; |
|
|
|
|
|
|
248
|
|
|
case 'N': $v = current(unpack('l', $val)) / pow(10, $type{1}); break; |
|
|
|
|
|
|
249
|
|
|
|
|
250
|
|
|
case 'c': $v = rtrim($val, ' '); break; |
|
|
|
|
|
|
251
|
|
|
case 'b': $v = $val; $l++; break; |
|
|
|
|
|
|
252
|
|
|
} |
|
253
|
|
|
$pos += $l; |
|
254
|
|
|
$unpacked[$name] = is_array($v) ? current($v) : $v; |
|
255
|
|
|
} |
|
256
|
|
|
return $unpacked; |
|
257
|
|
|
} |
|
258
|
|
|
|
|
259
|
|
|
public function get($ip){ |
|
260
|
|
|
return $this->max_city ? $this->getCity($ip) : $this->getCountry($ip); |
|
261
|
|
|
} |
|
262
|
|
View Code Duplication |
public function getCountry($ip){ |
|
263
|
|
|
if($this->max_city) { |
|
264
|
|
|
$tmp = $this->parseCity($this->get_num($ip)); |
|
265
|
|
|
return $tmp['country']['iso']; |
|
266
|
|
|
} |
|
267
|
|
|
else return $this->id2iso[$this->get_num($ip)]; |
|
268
|
|
|
} |
|
269
|
|
View Code Duplication |
public function getCountryId($ip){ |
|
270
|
|
|
if($this->max_city) { |
|
271
|
|
|
$tmp = $this->parseCity($this->get_num($ip)); |
|
272
|
|
|
return $tmp['country']['id']; |
|
273
|
|
|
} |
|
274
|
|
|
else return $this->get_num($ip); |
|
275
|
|
|
} |
|
276
|
|
|
public function getCity($ip){ |
|
277
|
|
|
$seek = $this->get_num($ip); |
|
278
|
|
|
return $seek ? $this->parseCity($seek) : false; |
|
279
|
|
|
} |
|
280
|
|
|
public function getCityFull($ip){ |
|
281
|
|
|
$seek = $this->get_num($ip); |
|
282
|
|
|
return $seek ? $this->parseCity($seek, 1) : false; |
|
|
|
|
|
|
283
|
|
|
} |
|
284
|
|
|
public function about(){ |
|
285
|
|
|
$charset = array('utf-8', 'latin1', 'cp1251'); |
|
286
|
|
|
$types = array('n/a', 'SxGeo Country', 'SxGeo City RU', 'SxGeo City EN', 'SxGeo City', 'SxGeo City Max RU', 'SxGeo City Max EN', 'SxGeo City Max'); |
|
287
|
|
|
return array( |
|
288
|
|
|
'Created' => date('Y.m.d', $this->info['time']), |
|
289
|
|
|
'Timestamp' => $this->info['time'], |
|
290
|
|
|
'Charset' => $charset[$this->info['charset']], |
|
291
|
|
|
'Type' => $types[$this->info['type']], |
|
292
|
|
|
'Byte Index' => $this->b_idx_len, |
|
293
|
|
|
'Main Index' => $this->m_idx_len, |
|
294
|
|
|
'Blocks In Index Item' => $this->range, |
|
295
|
|
|
'IP Blocks' => $this->db_items, |
|
296
|
|
|
'Block Size' => $this->block_len, |
|
297
|
|
|
'City' => array( |
|
298
|
|
|
'Max Length' => $this->max_city, |
|
299
|
|
|
'Total Size' => $this->info['city_size'], |
|
300
|
|
|
), |
|
301
|
|
|
'Region' => array( |
|
302
|
|
|
'Max Length' => $this->max_region, |
|
303
|
|
|
'Total Size' => $this->info['region_size'], |
|
304
|
|
|
), |
|
305
|
|
|
'Country' => array( |
|
306
|
|
|
'Max Length' => $this->max_country, |
|
307
|
|
|
'Total Size' => $this->info['country_size'], |
|
308
|
|
|
), |
|
309
|
|
|
); |
|
310
|
|
|
} |
|
311
|
|
|
} |
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
exitexpression 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.