PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,113 @@
<?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;
use Piwik\Common;
use Piwik\Config;
use Piwik\Piwik;
use Piwik\Plugins\UserCountry\LocationProvider;
/**
* The default LocationProvider, this LocationProvider guesses a visitor's country
* using the language they use. This provider is not very accurate.
*
*/
class DefaultProvider extends LocationProvider
{
const ID = 'default';
const TITLE = 'General_Default';
/**
* Guesses a visitor's location using a visitor's browser language.
*
* @param array $info Contains 'ip' & 'lang' keys.
* @return array Contains the guessed country code mapped to LocationProvider::COUNTRY_CODE_KEY.
*/
public function getLocation($info)
{
$enableLanguageToCountryGuess = Config::getInstance()->Tracker['enable_language_to_country_guess'];
if (empty($info['lang'])) {
$info['lang'] = Common::getBrowserLanguage();
}
$country = Common::getCountry($info['lang'], $enableLanguageToCountryGuess, $info['ip']);
$location = array(parent::COUNTRY_CODE_KEY => $country);
$this->completeLocationResult($location);
return $location;
}
/**
* Returns whether this location provider is available.
*
* This implementation is always available.
*
* @return bool always true
*/
public function isAvailable()
{
return true;
}
/**
* Returns whether this location provider is working correctly.
*
* This implementation is always working correctly.
*
* @return bool always true
*/
public function isWorking()
{
return true;
}
/**
* Returns an array describing the types of location information this provider will
* return.
*
* This provider supports the following types of location info:
* - continent code
* - continent name
* - country code
* - country name
*
* @return array
*/
public function getSupportedLocationInfo()
{
return array(self::CONTINENT_CODE_KEY => true,
self::CONTINENT_NAME_KEY => true,
self::COUNTRY_CODE_KEY => true,
self::COUNTRY_NAME_KEY => true);
}
/**
* Returns information about this location provider. Contains an id, title & description:
*
* array(
* 'id' => 'default',
* 'title' => '...',
* 'description' => '...'
* );
*
* @return array
*/
public function getInfo()
{
$desc = Piwik::translate('UserCountry_DefaultLocationProviderDesc1') . ' '
. Piwik::translate('UserCountry_DefaultLocationProviderDesc2',
array('<strong>', '', '', '</strong>'))
. '<p><a href="https://matomo.org/faq/how-to/#faq_163" rel="noreferrer noopener" target="_blank">'
. Piwik::translate('UserCountry_HowToInstallGeoIPDatabases')
. '</a></p>';
return array('id' => self::ID, 'title' => self::TITLE, 'description' => $desc, 'order' => 1);
}
}

View File

@ -0,0 +1,254 @@
<?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;
use Exception;
use Piwik\Piwik;
use Piwik\Plugin\Manager;
use Piwik\Plugins\UserCountry\LocationProvider;
/**
* Base type for all GeoIP LocationProviders.
*
*/
abstract class GeoIp extends LocationProvider
{
/* For testing, use: 'http://piwik-team.s3.amazonaws.com/GeoLiteCity.dat.gz' */
const GEO_LITE_URL = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz';
const TEST_IP = '194.57.91.215';
public static $geoIPDatabaseDir = 'misc';
/**
* Stores possible database file names categorized by the type of information
* GeoIP databases hold.
*
* @var array
*/
public static $dbNames = array(
'loc' => array('GeoIPCity.dat', 'GeoLiteCity.dat', 'GeoIP.dat'),
'isp' => array('GeoIPISP.dat'),
'org' => array('GeoIPOrg.dat'),
);
/**
* Cached region name array. Data is from geoipregionvars.php.
*
* @var array
*/
private static $regionNames = null;
/**
* Attempts to fill in some missing information in a GeoIP location.
*
* This method will call LocationProvider::completeLocationResult and then
* try to set the region name of the location if the country code & region
* code are set.
*
* @param array $location The location information to modify.
*/
public function completeLocationResult(&$location)
{
parent::completeLocationResult($location);
// set region name if region code is set
if (empty($location[self::REGION_NAME_KEY])
&& !empty($location[self::REGION_CODE_KEY])
&& !empty($location[self::COUNTRY_CODE_KEY])
) {
$countryCode = $location[self::COUNTRY_CODE_KEY];
$regionCode = (string)$location[self::REGION_CODE_KEY];
$location[self::REGION_NAME_KEY] = self::getRegionNameFromCodes($countryCode, $regionCode);
}
}
public function isVisible()
{
return !Manager::getInstance()->isPluginActivated('GeoIp2') || self::getCurrentProvider() instanceof GeoIp;
}
/**
* Returns true if this provider has been setup correctly, the error message if
* otherwise.
*
* @return bool|string
*/
public function isWorking()
{
// test with an example IP to make sure the provider is working
// NOTE: At the moment only country, region & city info is tested.
try {
$supportedInfo = $this->getSupportedLocationInfo();
list($testIp, $expectedResult) = self::getTestIpAndResult();
// get location using test IP
$location = $this->getLocation(array('ip' => $testIp));
// check that result is the same as expected
$isResultCorrect = true;
foreach ($expectedResult as $key => $value) {
// if this provider is not configured to support this information type, skip it
if (empty($supportedInfo[$key])) {
continue;
}
if (empty($location[$key])
|| $location[$key] != $value
) {
$isResultCorrect = false;
}
}
if (!$isResultCorrect) {
$unknown = Piwik::translate('General_Unknown');
$location = "'"
. (empty($location[self::CITY_NAME_KEY]) ? $unknown : $location[self::CITY_NAME_KEY])
. ", "
. (empty($location[self::REGION_CODE_KEY]) ? $unknown : $location[self::REGION_CODE_KEY])
. ", "
. (empty($location[self::COUNTRY_CODE_KEY]) ? $unknown : $location[self::COUNTRY_CODE_KEY])
. "'";
$expectedLocation = "'" . $expectedResult[self::CITY_NAME_KEY] . ", "
. $expectedResult[self::REGION_CODE_KEY] . ", "
. $expectedResult[self::COUNTRY_CODE_KEY] . "'";
$bind = array($testIp, $location, $expectedLocation);
return Piwik::translate('UserCountry_TestIPLocatorFailed', $bind);
}
return true;
} catch (Exception $ex) {
return $ex->getMessage();
}
}
/**
* Returns a region name for a country code + region code.
*
* @param string $countryCode
* @param string $regionCode
* @return string The region name or 'Unknown' (translated).
*/
public static function getRegionNameFromCodes($countryCode, $regionCode)
{
$regionNames = self::getRegionNames();
$countryCode = strtoupper($countryCode);
$regionCode = strtoupper($regionCode);
// ensure tibet is shown as region of china
if ($countryCode == 'TI' && $regionCode == '1') {
$regionCode = '14';
$countryCode = 'CN';
}
if (isset($regionNames[$countryCode][$regionCode])) {
return $regionNames[$countryCode][$regionCode];
} else {
return Piwik::translate('General_Unknown');
}
}
/**
* Returns an array of region names mapped by country code & region code.
*
* @return array
*/
public static function getRegionNames()
{
if (is_null(self::$regionNames)) {
$GEOIP_REGION_NAME = array();
require_once PIWIK_INCLUDE_PATH . '/libs/MaxMindGeoIP/geoipregionvars.php';
self::$regionNames = $GEOIP_REGION_NAME;
}
return self::$regionNames;
}
/**
* Returns the path of an existing GeoIP database or false if none can be found.
*
* @param array $possibleFileNames The list of possible file names for the GeoIP database.
* @return string|false
*/
public static function getPathToGeoIpDatabase($possibleFileNames)
{
foreach ($possibleFileNames as $filename) {
$path = self::getPathForGeoIpDatabase($filename);
if (file_exists($path)) {
return $path;
}
}
return false;
}
/**
* Returns full path for a GeoIP database managed by Piwik.
*
* @param string $filename Name of the .dat file.
* @return string
*/
public static function getPathForGeoIpDatabase($filename)
{
return PIWIK_INCLUDE_PATH . '/' . self::$geoIPDatabaseDir . '/' . $filename;
}
/**
* Returns test IP used by isWorking and expected result.
*
* @return array eg. array('1.2.3.4', array(self::COUNTRY_CODE_KEY => ...))
*/
private static function getTestIpAndResult()
{
static $result = null;
if (is_null($result)) {
// TODO: what happens when IP changes? should we get this information from piwik.org?
$expected = array(self::COUNTRY_CODE_KEY => 'FR',
self::REGION_CODE_KEY => 'A6',
self::CITY_NAME_KEY => 'Besançon');
$result = array(self::TEST_IP, $expected);
}
return $result;
}
/**
* Returns true if there is a GeoIP database in the 'misc' directory.
*
* @return bool
*/
public static function isDatabaseInstalled()
{
return self::getPathToGeoIpDatabase(self::$dbNames['loc'])
|| self::getPathToGeoIpDatabase(self::$dbNames['isp'])
|| self::getPathToGeoIpDatabase(self::$dbNames['org']);
}
/**
* Returns the type of GeoIP database ('loc', 'isp' or 'org') based on the
* filename (eg, 'GeoLiteCity.dat', 'GeoIPISP.dat', etc).
*
* @param string $filename
* @return string|false 'loc', 'isp', 'org', or false if cannot find a database
* type.
*/
public static function getGeoIPDatabaseTypeFromFilename($filename)
{
foreach (self::$dbNames as $key => $names) {
foreach ($names as $name) {
if ($name === $filename) {
return $key;
}
}
}
return false;
}
}

View File

@ -0,0 +1,328 @@
<?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\Piwik;
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
/**
* A LocationProvider that uses the PECL implementation of GeoIP.
*
* FIXME: For some reason, if the PECL module is loaded & an organization DB is available, the PHP
* module won't return organization info. If the PECL module is not loaded, organization info is returned.
*
*/
class Pecl extends GeoIp
{
const ID = 'geoip_pecl';
const TITLE = 'GeoIP Legacy (PECL)';
/**
* For tests.
*/
public static $forceDisable = false;
/**
* Uses the GeoIP PECL module to get a visitor's location based on their IP address.
*
* This function will return different results based on the data available. If a city
* database can be detected by the PECL module, it may return the country code,
* region code, city name, area code, latitude, longitude and postal code of the visitor.
*
* Alternatively, if only the country database can be detected, only the country code
* will be returned.
*
* The GeoIP PECL module will detect the following filenames:
* - GeoIP.dat
* - GeoIPCity.dat
* - GeoIPISP.dat
* - GeoIPOrg.dat
*
* Note how GeoLiteCity.dat, the name for the GeoLite city database, is not detected
* by the PECL module.
*
* @param array $info Must have an 'ip' field.
* @return array
*/
public function getLocation($info)
{
$ip = $this->getIpFromInfo($info);
$result = array();
// get location data
if (self::isCityDatabaseAvailable()) {
// Must hide errors because missing IPV6:
$location = @geoip_record_by_name($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'];
}
} else if (self::isRegionDatabaseAvailable()) {
$location = @geoip_region_by_name($ip);
if (!empty($location)) {
$result[self::REGION_CODE_KEY] = $location['region'];
$result[self::COUNTRY_CODE_KEY] = $location['country_code'];
}
} else {
$result[self::COUNTRY_CODE_KEY] = @geoip_country_code_by_name($ip);
}
// get organization data if the org database is available
if (self::isOrgDatabaseAvailable()) {
$org = @geoip_org_by_name($ip);
if ($org !== false) {
$result[self::ORG_KEY] = utf8_encode($org);
}
}
// get isp data if the isp database is available
if (self::isISPDatabaseAvailable()) {
$isp = @geoip_isp_by_name($ip);
if ($isp !== false) {
$result[self::ISP_KEY] = utf8_encode($isp);
}
}
if (empty($result)) {
return false;
}
$this->completeLocationResult($result);
return $result;
}
/**
* Returns true if the PECL module is installed and loaded, false if otherwise.
*
* @return bool
*/
public function isAvailable()
{
return !self::$forceDisable && function_exists('geoip_db_avail');
}
/**
* Returns true if the PECL module that is installed can be successfully used
* to get the location of an IP address.
*
* @return bool
*/
public function isWorking()
{
// if no no location database is available, this implementation is not setup correctly
if (!self::isLocationDatabaseAvailable()) {
$dbDir = dirname(geoip_db_filename(GEOIP_COUNTRY_EDITION)) . '/';
$quotedDir = "'$dbDir'";
// check if the directory the PECL module is looking for exists
if (!is_dir($dbDir)) {
return Piwik::translate('UserCountry_PeclGeoIPNoDBDir', array($quotedDir, "'geoip.custom_directory'"));
}
// check if the user named the city database GeoLiteCity.dat
if (file_exists($dbDir . 'GeoLiteCity.dat')) {
return Piwik::translate('UserCountry_PeclGeoLiteError',
array($quotedDir, "'GeoLiteCity.dat'", "'GeoIPCity.dat'"));
}
return Piwik::translate('UserCountry_CannotFindPeclGeoIPDb',
array($quotedDir, "'GeoIP.dat'", "'GeoIPCity.dat'"));
}
return parent::isWorking();
}
/**
* 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;
if (self::isCityDatabaseAvailable()) {
$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;
} else if (self::isRegionDatabaseAvailable()) {
$result[self::REGION_CODE_KEY] = true;
$result[self::REGION_NAME_KEY] = true;
}
// check if organization info is available
if (self::isOrgDatabaseAvailable()) {
$result[self::ORG_KEY] = true;
}
// check if ISP info is available
if (self::isISPDatabaseAvailable()) {
$result[self::ISP_KEY] = true;
}
return $result;
}
/**
* Returns information about this location provider. Contains an id, title & description:
*
* array(
* 'id' => 'geoip_pecl',
* 'title' => '...',
* 'description' => '...'
* );
*
* @return array
*/
public function getInfo()
{
$desc = Piwik::translate('UserCountry_GeoIpLocationProviderDesc_Pecl1') . '<br/><br/>'
. Piwik::translate('UserCountry_GeoIpLocationProviderDesc_Pecl2');
$installDocs = '<a rel="noreferrer noopener" target="_blank" href="https://matomo.org/faq/how-to/#faq_164">'
. Piwik::translate('UserCountry_HowToInstallGeoIpPecl')
. '</a>';
$extraMessage = false;
if ($this->isAvailable()) {
$peclDir = ini_get('geoip.custom_directory');
if ($peclDir === false) {
$extraMessage = Piwik::translate('UserCountry_GeoIPPeclCustomDirNotSet', "'geoip.custom_directory'");
} else {
$extraMessage = 'The \'geoip.custom_directory\' PHP ini option is set to \'' . $peclDir . '\'.';
}
$availableDatabaseTypes = array();
if (self::isCityDatabaseAvailable()) {
$availableDatabaseTypes[] = Piwik::translate('UserCountry_City');
}
if (self::isRegionDatabaseAvailable()) {
$availableDatabaseTypes[] = Piwik::translate('UserCountry_Region');
}
if (self::isCountryDatabaseAvailable()) {
$availableDatabaseTypes[] = Piwik::translate('UserCountry_Country');
}
if (self::isISPDatabaseAvailable()) {
$availableDatabaseTypes[] = 'ISP';
}
if (self::isOrgDatabaseAvailable()) {
$availableDatabaseTypes[] = Piwik::translate('UserCountry_Organization');
}
$extraMessage .= '<br/><br/>' . Piwik::translate('UserCountry_GeoIPImplHasAccessTo') . ':&nbsp;<strong>'
. implode(', ', $availableDatabaseTypes) . '</strong>.';
$extraMessage = '<strong>' . Piwik::translate('General_Note') . ':&nbsp;</strong>' . $extraMessage;
}
return array('id' => self::ID,
'title' => self::TITLE,
'description' => $desc,
'install_docs' => $installDocs,
'extra_message' => $extraMessage,
'order' => 13);
}
/**
* Returns true if the PECL module can detect a location database (either a country,
* region or city will do).
*
* @return bool
*/
public static function isLocationDatabaseAvailable()
{
return self::isCityDatabaseAvailable()
|| self::isRegionDatabaseAvailable()
|| self::isCountryDatabaseAvailable();
}
/**
* Returns true if the PECL module can detect a city database.
*
* @return bool
*/
public static function isCityDatabaseAvailable()
{
return geoip_db_avail(GEOIP_CITY_EDITION_REV0)
|| geoip_db_avail(GEOIP_CITY_EDITION_REV1);
}
/**
* Returns true if the PECL module can detect a region database.
*
* @return bool
*/
public static function isRegionDatabaseAvailable()
{
return geoip_db_avail(GEOIP_REGION_EDITION_REV0)
|| geoip_db_avail(GEOIP_REGION_EDITION_REV1);
}
/**
* Returns true if the PECL module can detect a country database.
*
* @return bool
*/
public static function isCountryDatabaseAvailable()
{
return geoip_db_avail(GEOIP_COUNTRY_EDITION);
}
/**
* Returns true if the PECL module can detect an organization database.
*
* @return bool
*/
public static function isOrgDatabaseAvailable()
{
return geoip_db_avail(GEOIP_ORG_EDITION);
}
/**
* Returns true if the PECL module can detect an ISP database.
*
* @return bool
*/
public static function isISPDatabaseAvailable()
{
return geoip_db_avail(GEOIP_ISP_EDITION);
}
}

View File

@ -0,0 +1,388 @@
<?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 Legacy (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>', '</strong>', '<strong>', '</strong>'));
$installDocs = '<a rel="noreferrer noopener" target="_blank" href="https://matomo.org/faq/how-to/#faq_163">'
. Piwik::translate('UserCountry_HowToInstallGeoIPDatabases')
. '</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');
}
if (!empty($availableDatabaseTypes)) {
$extraMessage = '<strong>' . Piwik::translate('General_Note') . '</strong>:&nbsp;'
. Piwik::translate('UserCountry_GeoIPImplHasAccessTo') . ':&nbsp;<strong>'
. implode(', ', $availableDatabaseTypes) . '</strong>.';
} else {
$extraMessage = '<strong>' . Piwik::translate('General_Note') . '</strong>:&nbsp;'
. Piwik::translate('UserCountry_GeoIPNoDatabaseFound') . '<strong>';
}
return array('id' => self::ID,
'title' => self::TITLE,
'description' => $desc,
'install_docs' => $installDocs,
'extra_message' => $extraMessage,
'order' => 12);
}
/**
* 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];
}
}

View File

@ -0,0 +1,291 @@
<?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\Common;
use Piwik\IP;
use Piwik\Piwik;
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
use Piwik\Plugins\UserCountry\LocationProvider;
/**
* A LocationProvider that uses an GeoIP module installed in an HTTP Server.
*
* To make this provider available, make sure the GEOIP_ADDR server
* variable is set.
*
*/
class ServerBased extends GeoIp
{
const ID = 'geoip_serverbased';
const TITLE = 'GeoIP Legacy (%s)';
const TEST_SERVER_VAR = 'GEOIP_ADDR';
const TEST_SERVER_VAR_ALT = 'GEOIP_COUNTRY_CODE';
const TEST_SERVER_VAR_ALT_IPV6 = 'GEOIP_COUNTRY_CODE_V6';
private static $geoIpServerVars = array(
parent::COUNTRY_CODE_KEY => 'GEOIP_COUNTRY_CODE',
parent::COUNTRY_NAME_KEY => 'GEOIP_COUNTRY_NAME',
parent::REGION_CODE_KEY => 'GEOIP_REGION',
parent::REGION_NAME_KEY => 'GEOIP_REGION_NAME',
parent::AREA_CODE_KEY => 'GEOIP_AREA_CODE',
parent::LATITUDE_KEY => 'GEOIP_LATITUDE',
parent::LONGITUDE_KEY => 'GEOIP_LONGITUDE',
parent::POSTAL_CODE_KEY => 'GEOIP_POSTAL_CODE',
);
private static $geoIpUtfServerVars = array(
parent::CITY_NAME_KEY => 'GEOIP_CITY',
parent::ISP_KEY => 'GEOIP_ISP',
parent::ORG_KEY => 'GEOIP_ORGANIZATION',
);
/**
* 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 and based
* on how the GeoIP module is configured.
*
* If a region database is used, it may return the country code, region code,
* city name, area code, latitude, longitude and postal code of the visitor.
*
* Alternatively, only the country code may be returned for another database.
*
* If your HTTP server is not configured to include all GeoIP information, some
* information will not be available to Piwik.
*
* @param array $info Must have an 'ip' field.
* @return array
*/
public function getLocation($info)
{
$ip = $this->getIpFromInfo($info);
// geoip modules that are built into servers can't use a forced IP. in this case we try
// to fallback to another version.
$myIP = IP::getIpFromHeader();
if (!self::isSameOrAnonymizedIp($ip, $myIP)
&& (!isset($info['disable_fallbacks'])
|| !$info['disable_fallbacks'])
) {
Common::printDebug("The request is for IP address: " . $info['ip'] . " but your IP is: $myIP. GeoIP Server Module (apache/nginx) does not support this use case... ");
$fallbacks = array(
Pecl::ID,
Php::ID
);
foreach ($fallbacks as $fallbackProviderId) {
$otherProvider = LocationProvider::getProviderById($fallbackProviderId);
if ($otherProvider) {
Common::printDebug("Used $fallbackProviderId to detect this visitor IP");
return $otherProvider->getLocation($info);
}
}
Common::printDebug("FAILED to lookup the geo location of this IP address, as no fallback location providers is configured. We recommend to configure Geolocation PECL module to fix this error.");
return false;
}
$result = array();
foreach (self::$geoIpServerVars as $resultKey => $geoipVarName) {
if (!empty($_SERVER[$geoipVarName])) {
$result[$resultKey] = $_SERVER[$geoipVarName];
}
$geoipVarNameV6 = $geoipVarName . '_V6';
if (!empty($_SERVER[$geoipVarNameV6])) {
$result[$resultKey] = $_SERVER[$geoipVarNameV6];
}
}
foreach (self::$geoIpUtfServerVars as $resultKey => $geoipVarName) {
if (!empty($_SERVER[$geoipVarName])) {
$result[$resultKey] = utf8_encode($_SERVER[$geoipVarName]);
}
}
$this->completeLocationResult($result);
return $result;
}
/**
* Returns an array describing the types of location information this provider will
* return.
*
* There's no way to tell exactly what database the HTTP server is using, so we just
* assume country and continent information is available. This can make diagnostics
* a bit more difficult, unfortunately.
*
* @return array
*/
public function getSupportedLocationInfo()
{
$result = array();
// assume country info is always available. it's an error if it's not.
$result[self::COUNTRY_CODE_KEY] = true;
$result[self::COUNTRY_NAME_KEY] = true;
$result[self::CONTINENT_CODE_KEY] = true;
$result[self::CONTINENT_NAME_KEY] = true;
return $result;
}
/**
* Checks if an HTTP server module has been installed. It checks by looking for
* the GEOIP_ADDR server variable.
*
* There's a special check for the Apache module, but we can't check specifically
* for anything else.
*
* @return bool|string
*/
public function isAvailable()
{
// check if apache module is installed
if (function_exists('apache_get_modules')) {
foreach (apache_get_modules() as $name) {
if (strpos($name, 'geoip') !== false) {
return true;
}
}
}
$available = !empty($_SERVER[self::TEST_SERVER_VAR])
|| !empty($_SERVER[self::TEST_SERVER_VAR_ALT])
|| !empty($_SERVER[self::TEST_SERVER_VAR_ALT_IPV6])
;
if ($available) {
return true;
}
// if not available return message w/ extra info
if (!function_exists('apache_get_modules')) {
return Piwik::translate('General_Note') . ':&nbsp;' . Piwik::translate('UserCountry_AssumingNonApache');
}
$message = "<strong>" . Piwik::translate('General_Note') . ':&nbsp;'
. Piwik::translate('UserCountry_FoundApacheModules')
. "</strong>:<br/><br/>\n<ul style=\"list-style:disc;margin-left:24px\">\n";
foreach (apache_get_modules() as $name) {
$message .= "<li>$name</li>\n";
}
$message .= "</ul>";
return $message;
}
/**
* Returns true if the GEOIP_ADDR server variable is defined.
*
* @return bool
*/
public function isWorking()
{
if (empty($_SERVER[self::TEST_SERVER_VAR])
&& empty($_SERVER[self::TEST_SERVER_VAR_ALT])
&& empty($_SERVER[self::TEST_SERVER_VAR_ALT_IPV6])
) {
return Piwik::translate("UserCountry_CannotFindGeoIPServerVar", self::TEST_SERVER_VAR . ' $_SERVER');
}
return true; // can't check for another IP
}
/**
* Returns information about this location provider. Contains an id, title & description:
*
* array(
* 'id' => 'geoip_serverbased',
* 'title' => '...',
* 'description' => '...'
* );
*
* @return array
*/
public function getInfo()
{
if (function_exists('apache_note')) {
$serverDesc = 'Apache';
} else {
$serverDesc = Piwik::translate('UserCountry_HttpServerModule');
}
$title = sprintf(self::TITLE, $serverDesc);
$desc = Piwik::translate('UserCountry_GeoIpLocationProviderDesc_ServerBased1', array('<strong>', '</strong>'))
. '<br/><br/>'
. Piwik::translate('UserCountry_GeoIpLocationProviderDesc_ServerBasedAnonWarn')
. '<br/><br/>'
. Piwik::translate('UserCountry_GeoIpLocationProviderDesc_ServerBased2',
array('<strong>', '</strong>', '<strong>', '</strong>'));
$installDocs =
'<a rel="noreferrer noopener" target="_blank" href="https://matomo.org/faq/how-to/#faq_165">'
. Piwik::translate('UserCountry_HowToInstallApacheModule')
. '</a><br/>'
. '<a rel="noreferrer noopener" target="_blank" href="https://matomo.org/faq/how-to/#faq_166">'
. Piwik::translate('UserCountry_HowToInstallNginxModule')
. '</a>';
$geoipServerVars = array();
foreach ($_SERVER as $key => $value) {
if (strpos($key, 'GEOIP') === 0) {
$geoipServerVars[] = $key;
}
}
if (empty($geoipServerVars)) {
$extraMessage = '<strong>' . Piwik::translate('UserCountry_GeoIPNoServerVars', '$_SERVER') . '</strong>';
} else {
$extraMessage = '<strong>' . Piwik::translate('UserCountry_GeoIPServerVarsFound', '$_SERVER')
. ":</strong><br/><br/>\n<ul style=\"list-style:disc;margin-left:24px\">\n";
foreach ($geoipServerVars as $key) {
$extraMessage .= '<li>' . $key . "</li>\n";
}
$extraMessage .= '</ul>';
}
return array('id' => self::ID,
'title' => $title,
'description' => $desc,
'order' => 14,
'install_docs' => $installDocs,
'extra_message' => $extraMessage);
}
/**
* Checks if two IP addresses are the same or if the first is the anonymized
* version of the other.
*
* @param string $ip
* @param string $currentIp This IP should not be anonymized.
* @return bool
*/
public static function isSameOrAnonymizedIp($ip, $currentIp)
{
$ip = array_reverse(explode('.', $ip));
$currentIp = array_reverse(explode('.', $currentIp));
if (count($ip) != count($currentIp)) {
return false;
}
foreach ($ip as $i => $byte) {
if ($byte == 0) {
$currentIp[$i] = 0;
} else {
break;
}
}
foreach ($ip as $i => $byte) {
if ($byte != $currentIp[$i]) {
return false;
}
}
return true;
}
}