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
|
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
use Piwik\Log;
use Piwik\Piwik;
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
/**
* A LocationProvider that uses the PHP implementation of GeoIP.
*
*/
class Php extends GeoIp
{
const ID = 'geoip_php';
const TITLE = 'GeoIP (Php)';
/**
* The GeoIP database instances used. This array will contain at most three
* of them: one for location info, one for ISP info and another for organization
* info.
*
* Each instance is mapped w/ one of the following keys: 'loc', 'isp', 'org'
*
* @var array of GeoIP instances
*/
private $geoIpCache = array();
/**
* Possible filenames for each type of GeoIP database. When looking for a database
* file in the 'misc' subdirectory, files with these names will be looked for.
*
* This variable is an array mapping either the 'loc', 'isp' or 'org' strings with
* an array of filenames.
*
* By default, this will be set to Php::$dbNames.
*
* @var array
*/
private $customDbNames;
/**
* Constructor.
*
* @param array|bool $customDbNames The possible filenames for each type of GeoIP database.
* eg array(
* 'loc' => array('GeoLiteCity.dat'),
* 'isp' => array('GeoIP.dat', 'GeoIPISP.dat')
* 'org' => array('GeoIPOrg.dat')
* )
* If a key is missing (or the parameter not supplied), then the
* default database names are used.
*/
public function __construct($customDbNames = false)
{
$this->customDbNames = parent::$dbNames;
if ($customDbNames !== false) {
foreach ($this->customDbNames as $key => $names) {
if (isset($customDbNames[$key])) {
$this->customDbNames[$key] = $customDbNames[$key];
}
}
}
}
/**
* Closes all open geoip instances.
*/
public function __destruct()
{
foreach ($this->geoIpCache as $instance) {
geoip_close($instance);
}
}
/**
* Uses a GeoIP database to get a visitor's location based on their IP address.
*
* This function will return different results based on the data used. If a city
* database is used, it may return the country code, region code, city name, area
* code, latitude, longitude and postal code of the visitor.
*
* Alternatively, if used with a country database, only the country code will be
* returned.
*
* @param array $info Must have an 'ip' field.
* @return array
*/
public function getLocation($info)
{
$ip = $this->getIpFromInfo($info);
$isIPv6 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
$result = array();
$locationGeoIp = $this->getGeoIpInstance($key = 'loc');
if ($locationGeoIp) {
switch ($locationGeoIp->databaseType) {
case GEOIP_CITY_EDITION_REV0: // city database type
case GEOIP_CITY_EDITION_REV1:
case GEOIP_CITYCOMBINED_EDITION:
if ($isIPv6) {
$location = geoip_record_by_addr_v6($locationGeoIp, $ip);
} else {
$location = geoip_record_by_addr($locationGeoIp, $ip);
}
if (!empty($location)) {
$result[self::COUNTRY_CODE_KEY] = $location->country_code;
$result[self::REGION_CODE_KEY] = $location->region;
$result[self::CITY_NAME_KEY] = utf8_encode($location->city);
$result[self::AREA_CODE_KEY] = $location->area_code;
$result[self::LATITUDE_KEY] = $location->latitude;
$result[self::LONGITUDE_KEY] = $location->longitude;
$result[self::POSTAL_CODE_KEY] = $location->postal_code;
}
break;
case GEOIP_REGION_EDITION_REV0: // region database type
case GEOIP_REGION_EDITION_REV1:
if ($isIPv6) {
// NOTE: geoip_region_by_addr_v6 does not exist (yet?), so we
// return the country code and an empty region code
$location = array(geoip_country_code_by_addr_v6($locationGeoIp, $ip), '');
} else {
$location = geoip_region_by_addr($locationGeoIp, $ip);
}
if (!empty($location)) {
$result[self::COUNTRY_CODE_KEY] = $location[0];
$result[self::REGION_CODE_KEY] = $location[1];
}
break;
case GEOIP_COUNTRY_EDITION: // country database type
if ($isIPv6) {
$result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr_v6($locationGeoIp, $ip);
} else {
$result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr($locationGeoIp, $ip);
}
break;
default: // unknown database type, log warning and fallback to country edition
Log::warning("Found unrecognized database type: %s", $locationGeoIp->databaseType);
if ($isIPv6) {
$result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr_v6($locationGeoIp, $ip);
} else {
$result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr($locationGeoIp, $ip);
}
break;
}
}
// NOTE: ISP & ORG require commercial dbs to test. The code has been tested manually,
// but not by system tests.
$ispGeoIp = $this->getGeoIpInstance($key = 'isp');
if ($ispGeoIp) {
if ($isIPv6) {
$isp = geoip_name_by_addr_v6($ispGeoIp, $ip);
} else {
$isp = geoip_org_by_addr($ispGeoIp, $ip);
}
if (!empty($isp)) {
$result[self::ISP_KEY] = utf8_encode($isp);
}
}
$orgGeoIp = $this->getGeoIpInstance($key = 'org');
if ($orgGeoIp) {
if ($isIPv6) {
$org = geoip_name_by_addr_v6($orgGeoIp, $ip);
} else {
$org = geoip_org_by_addr($orgGeoIp, $ip);
}
if (!empty($org)) {
$result[self::ORG_KEY] = utf8_encode($org);
}
}
if (empty($result)) {
return false;
}
$this->completeLocationResult($result);
return $result;
}
/**
* Returns true if this location provider is available. Piwik ships w/ the MaxMind
* PHP library, so this provider is available if a location GeoIP database can be found.
*
* @return bool
*/
public function isAvailable()
{
$path = self::getPathToGeoIpDatabase($this->customDbNames['loc']);
return $path !== false;
}
/**
* Returns true if this provider has been setup correctly, the error message if
* otherwise.
*
* @return bool|string
*/
public function isWorking()
{
if (!function_exists('mb_internal_encoding')) {
return Piwik::translate('UserCountry_GeoIPCannotFindMbstringExtension',
array('mb_internal_encoding', 'mbstring'));
}
$geoIpError = false;
$catchGeoIpError = function ($errno, $errstr, $errfile, $errline) use (&$geoIpError) {
$filename = basename($errfile);
if ($filename == 'geoip.inc'
|| $filename == 'geoipcity.inc'
) {
$geoIpError = array($errno, $errstr, $errfile, $errline);
} else {
throw new \Exception("Error in PHP GeoIP provider: $errstr on line $errline of $errfile"); // unexpected
}
};
// catch GeoIP errors
set_error_handler($catchGeoIpError);
$result = parent::isWorking();
restore_error_handler();
if ($geoIpError) {
list($errno, $errstr, $errfile, $errline) = $geoIpError;
Log::warning("Got GeoIP error when testing PHP GeoIP location provider: %s(%s): %s", $errfile, $errline, $errstr);
return Piwik::translate('UserCountry_GeoIPIncorrectDatabaseFormat');
}
return $result;
}
/**
* Returns an array describing the types of location information this provider will
* return.
*
* The location info this provider supports depends on what GeoIP databases it can
* find.
*
* This provider will always support country & continent information.
*
* If a region database is found, then region code & name information will be
* supported.
*
* If a city database is found, then region code, region name, city name,
* area code, latitude, longitude & postal code are all supported.
*
* If an organization database is found, organization information is
* supported.
*
* If an ISP database is found, ISP information is supported.
*
* @return array
*/
public function getSupportedLocationInfo()
{
$result = array();
// country & continent info always available
$result[self::CONTINENT_CODE_KEY] = true;
$result[self::CONTINENT_NAME_KEY] = true;
$result[self::COUNTRY_CODE_KEY] = true;
$result[self::COUNTRY_NAME_KEY] = true;
$locationGeoIp = $this->getGeoIpInstance($key = 'loc');
if ($locationGeoIp) {
switch ($locationGeoIp->databaseType) {
case GEOIP_CITY_EDITION_REV0: // city database type
case GEOIP_CITY_EDITION_REV1:
case GEOIP_CITYCOMBINED_EDITION:
$result[self::REGION_CODE_KEY] = true;
$result[self::REGION_NAME_KEY] = true;
$result[self::CITY_NAME_KEY] = true;
$result[self::AREA_CODE_KEY] = true;
$result[self::LATITUDE_KEY] = true;
$result[self::LONGITUDE_KEY] = true;
$result[self::POSTAL_CODE_KEY] = true;
break;
case GEOIP_REGION_EDITION_REV0: // region database type
case GEOIP_REGION_EDITION_REV1:
$result[self::REGION_CODE_KEY] = true;
$result[self::REGION_NAME_KEY] = true;
break;
default: // country or unknown database type
break;
}
}
// check if isp info is available
if ($this->getGeoIpInstance($key = 'isp')) {
$result[self::ISP_KEY] = true;
}
// check of org info is available
if ($this->getGeoIpInstance($key = 'org')) {
$result[self::ORG_KEY] = true;
}
return $result;
}
/**
* Returns information about this location provider. Contains an id, title & description:
*
* array(
* 'id' => 'geoip_php',
* 'title' => '...',
* 'description' => '...'
* );
*
* @return array
*/
public function getInfo()
{
$desc = Piwik::translate('UserCountry_GeoIpLocationProviderDesc_Php1') . '<br/><br/>'
. Piwik::translate('UserCountry_GeoIpLocationProviderDesc_Php2',
array('<strong><em>', '</em></strong>', '<strong><em>', '</em></strong>'));
$installDocs = '<em><a rel="noreferrer" target="_blank" href="http://piwik.org/faq/how-to/#faq_163">'
. Piwik::translate('UserCountry_HowToInstallGeoIPDatabases')
. '</em></a>';
$availableDatabaseTypes = array();
if (self::getPathToGeoIpDatabase(array('GeoIPCity.dat', 'GeoLiteCity.dat')) !== false) {
$availableDatabaseTypes[] = Piwik::translate('UserCountry_City');
}
if (self::getPathToGeoIpDatabase(array('GeoIPRegion.dat')) !== false) {
$availableDatabaseTypes[] = Piwik::translate('UserCountry_Region');
}
if (self::getPathToGeoIpDatabase(array('GeoIPCountry.dat')) !== false) {
$availableDatabaseTypes[] = Piwik::translate('UserCountry_Country');
}
if (self::getPathToGeoIpDatabase(array('GeoIPISP.dat')) !== false) {
$availableDatabaseTypes[] = 'ISP';
}
if (self::getPathToGeoIpDatabase(array('GeoIPOrg.dat')) !== false) {
$availableDatabaseTypes[] = Piwik::translate('UserCountry_Organization');
}
$extraMessage = '<strong><em>' . Piwik::translate('General_Note') . '</em></strong>: '
. Piwik::translate('UserCountry_GeoIPImplHasAccessTo') . ': <strong><em>'
. implode(', ', $availableDatabaseTypes) . '</em></strong>.';
return array('id' => self::ID,
'title' => self::TITLE,
'description' => $desc,
'install_docs' => $installDocs,
'extra_message' => $extraMessage,
'order' => 2);
}
/**
* Returns a GeoIP instance. Creates it if necessary.
*
* @param string $key 'loc', 'isp' or 'org'. Determines the type of GeoIP database
* to load.
* @return object|false
*/
private function getGeoIpInstance($key)
{
if (empty($this->geoIpCache[$key])) {
// make sure region names are loaded & saved first
parent::getRegionNames();
require_once PIWIK_INCLUDE_PATH . '/libs/MaxMindGeoIP/geoipcity.inc';
$pathToDb = self::getPathToGeoIpDatabase($this->customDbNames[$key]);
if ($pathToDb !== false) {
$this->geoIpCache[$key] = geoip_open($pathToDb, GEOIP_STANDARD); // TODO support shared memory
}
}
return empty($this->geoIpCache[$key]) ? false : $this->geoIpCache[$key];
}
}
|