add icons for Character groups
211
www/analytics/plugins/UserCountry/API.php
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\UserCountry;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Archive;
|
||||
use Piwik\DataTable;
|
||||
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider;
|
||||
use Piwik\Tracker\Visit;
|
||||
|
||||
/**
|
||||
* @see plugins/UserCountry/functions.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/functions.php';
|
||||
|
||||
/**
|
||||
* The UserCountry API lets you access reports about your visitors' Countries and Continents.
|
||||
* @method static \Piwik\Plugins\UserCountry\API getInstance()
|
||||
*/
|
||||
class API extends \Piwik\Plugin\API
|
||||
{
|
||||
public function getCountry($idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$dataTable = $this->getDataTable(Archiver::COUNTRY_RECORD_NAME, $idSite, $period, $date, $segment);
|
||||
|
||||
// apply filter on the whole datatable in order the inline search to work (searches are done on "beautiful" label)
|
||||
$dataTable->filter('ColumnCallbackAddMetadata', array('label', 'code'));
|
||||
$dataTable->filter('ColumnCallbackAddMetadata', array('label', 'logo', __NAMESPACE__ . '\getFlagFromCode'));
|
||||
$dataTable->filter('ColumnCallbackReplace', array('label', __NAMESPACE__ . '\countryTranslate'));
|
||||
|
||||
$dataTable->queueFilter('ColumnCallbackAddMetadata', array(array(), 'logoWidth', function () { return 16; }));
|
||||
$dataTable->queueFilter('ColumnCallbackAddMetadata', array(array(), 'logoHeight', function () { return 11; }));
|
||||
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getContinent($idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$dataTable = $this->getDataTable(Archiver::COUNTRY_RECORD_NAME, $idSite, $period, $date, $segment);
|
||||
|
||||
$getContinent = array('Piwik\Common', 'getContinent');
|
||||
$dataTable->filter('GroupBy', array('label', $getContinent));
|
||||
|
||||
$dataTable->filter('ColumnCallbackReplace', array('label', __NAMESPACE__ . '\continentTranslate'));
|
||||
$dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'code'));
|
||||
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns visit information for every region with at least one visit.
|
||||
*
|
||||
* @param int|string $idSite
|
||||
* @param string $period
|
||||
* @param string $date
|
||||
* @param string|bool $segment
|
||||
* @return DataTable
|
||||
*/
|
||||
public function getRegion($idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$dataTable = $this->getDataTable(Archiver::REGION_RECORD_NAME, $idSite, $period, $date, $segment);
|
||||
|
||||
$separator = Archiver::LOCATION_SEPARATOR;
|
||||
$unk = Visit::UNKNOWN_CODE;
|
||||
|
||||
// split the label and put the elements into the 'region' and 'country' metadata fields
|
||||
$dataTable->filter('ColumnCallbackAddMetadata',
|
||||
array('label', 'region', __NAMESPACE__ . '\getElementFromStringArray', array($separator, 0, $unk)));
|
||||
$dataTable->filter('ColumnCallbackAddMetadata',
|
||||
array('label', 'country', __NAMESPACE__ . '\getElementFromStringArray', array($separator, 1, $unk)));
|
||||
|
||||
// add country name metadata
|
||||
$dataTable->filter('MetadataCallbackAddMetadata',
|
||||
array('country', 'country_name', __NAMESPACE__ . '\CountryTranslate', $applyToSummaryRow = false));
|
||||
|
||||
// get the region name of each row and put it into the 'region_name' metadata
|
||||
$dataTable->filter('ColumnCallbackAddMetadata',
|
||||
array('label', 'region_name', __NAMESPACE__ . '\getRegionName', $params = null,
|
||||
$applyToSummaryRow = false));
|
||||
|
||||
// add the country flag as a url to the 'logo' metadata field
|
||||
$dataTable->filter('MetadataCallbackAddMetadata', array('country', 'logo', __NAMESPACE__ . '\getFlagFromCode'));
|
||||
|
||||
// prettify the region label
|
||||
$dataTable->filter('ColumnCallbackReplace', array('label', __NAMESPACE__ . '\getPrettyRegionName'));
|
||||
|
||||
$dataTable->queueFilter('ReplaceSummaryRowLabel');
|
||||
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns visit information for every city with at least one visit.
|
||||
*
|
||||
* @param int|string $idSite
|
||||
* @param string $period
|
||||
* @param string $date
|
||||
* @param string|bool $segment
|
||||
* @return DataTable
|
||||
*/
|
||||
public function getCity($idSite, $period, $date, $segment = false)
|
||||
{
|
||||
$dataTable = $this->getDataTable(Archiver::CITY_RECORD_NAME, $idSite, $period, $date, $segment);
|
||||
|
||||
$separator = Archiver::LOCATION_SEPARATOR;
|
||||
$unk = Visit::UNKNOWN_CODE;
|
||||
|
||||
// split the label and put the elements into the 'city_name', 'region', 'country',
|
||||
// 'lat' & 'long' metadata fields
|
||||
$strUnknown = Piwik::translate('General_Unknown');
|
||||
$dataTable->filter('ColumnCallbackAddMetadata',
|
||||
array('label', 'city_name', __NAMESPACE__ . '\getElementFromStringArray',
|
||||
array($separator, 0, $strUnknown)));
|
||||
$dataTable->filter('MetadataCallbackAddMetadata',
|
||||
array('city_name', 'city', function ($city) use ($strUnknown) {
|
||||
if ($city == $strUnknown) {
|
||||
return "xx";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
$dataTable->filter('ColumnCallbackAddMetadata',
|
||||
array('label', 'region', __NAMESPACE__ . '\getElementFromStringArray', array($separator, 1, $unk)));
|
||||
$dataTable->filter('ColumnCallbackAddMetadata',
|
||||
array('label', 'country', __NAMESPACE__ . '\getElementFromStringArray', array($separator, 2, $unk)));
|
||||
|
||||
// backwards compatibility: for reports that have lat|long in label
|
||||
$dataTable->filter('ColumnCallbackAddMetadata',
|
||||
array('label', 'lat', __NAMESPACE__ . '\getElementFromStringArray', array($separator, 3, false)));
|
||||
$dataTable->filter('ColumnCallbackAddMetadata',
|
||||
array('label', 'long', __NAMESPACE__ . '\getElementFromStringArray', array($separator, 4, false)));
|
||||
|
||||
// add country name & region name metadata
|
||||
$dataTable->filter('MetadataCallbackAddMetadata',
|
||||
array('country', 'country_name', __NAMESPACE__ . '\countryTranslate', $applyToSummaryRow = false));
|
||||
|
||||
$getRegionName = '\\Piwik\\Plugins\\UserCountry\\LocationProvider\\GeoIp::getRegionNameFromCodes';
|
||||
$dataTable->filter('MetadataCallbackAddMetadata', array(
|
||||
array('country', 'region'), 'region_name', $getRegionName, $applyToSummaryRow = false));
|
||||
|
||||
// add the country flag as a url to the 'logo' metadata field
|
||||
$dataTable->filter('MetadataCallbackAddMetadata', array('country', 'logo', __NAMESPACE__ . '\getFlagFromCode'));
|
||||
|
||||
// prettify the label
|
||||
$dataTable->filter('ColumnCallbackReplace', array('label', __NAMESPACE__ . '\getPrettyCityName'));
|
||||
|
||||
$dataTable->queueFilter('ReplaceSummaryRowLabel');
|
||||
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a location provider to find/guess the location of an IP address.
|
||||
*
|
||||
* See LocationProvider::getLocation to see the details
|
||||
* of the result of this function.
|
||||
*
|
||||
* @param string $ip The IP address.
|
||||
* @param bool|string $provider The ID of the provider to use or false to use the
|
||||
* currently configured one.
|
||||
* @throws Exception
|
||||
* @return array|false
|
||||
*/
|
||||
public function getLocationFromIP($ip, $provider = false)
|
||||
{
|
||||
Piwik::checkUserHasSomeViewAccess();
|
||||
|
||||
if ($provider === false) {
|
||||
$provider = LocationProvider::getCurrentProviderId();
|
||||
}
|
||||
|
||||
$oProvider = LocationProvider::getProviderById($provider);
|
||||
if ($oProvider === false) {
|
||||
throw new Exception("Cannot find the '$provider' provider. It is either an invalid provider "
|
||||
. "ID or the ID of a provider that is not working.");
|
||||
}
|
||||
|
||||
$location = $oProvider->getLocation(array('ip' => $ip));
|
||||
if (empty($location)) {
|
||||
throw new Exception("Could not geolocate '$ip'!");
|
||||
}
|
||||
$location['ip'] = $ip;
|
||||
return $location;
|
||||
}
|
||||
|
||||
protected function getDataTable($name, $idSite, $period, $date, $segment)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
$archive = Archive::build($idSite, $period, $date, $segment);
|
||||
$dataTable = $archive->getDataTable($name);
|
||||
$dataTable->filter('Sort', array(Metrics::INDEX_NB_VISITS));
|
||||
$dataTable->queueFilter('ReplaceColumnNames');
|
||||
return $dataTable;
|
||||
}
|
||||
|
||||
public function getNumberOfDistinctCountries($idSite, $period, $date, $segment = false)
|
||||
{
|
||||
Piwik::checkUserHasViewAccess($idSite);
|
||||
$archive = Archive::build($idSite, $period, $date, $segment);
|
||||
return $archive->getDataTableFromNumeric(Archiver::DISTINCT_COUNTRIES_METRIC);
|
||||
}
|
||||
}
|
||||
179
www/analytics/plugins/UserCountry/Archiver.php
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\UserCountry;
|
||||
|
||||
use Piwik\ArchiveProcessor;
|
||||
use Piwik\DataArray;
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Metrics;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider;
|
||||
|
||||
class Archiver extends \Piwik\Plugin\Archiver
|
||||
{
|
||||
const COUNTRY_RECORD_NAME = 'UserCountry_country';
|
||||
const REGION_RECORD_NAME = 'UserCountry_region';
|
||||
const CITY_RECORD_NAME = 'UserCountry_city';
|
||||
const DISTINCT_COUNTRIES_METRIC = 'UserCountry_distinctCountries';
|
||||
|
||||
// separate region, city & country info in stored report labels
|
||||
const LOCATION_SEPARATOR = '|';
|
||||
|
||||
private $latLongForCities = array();
|
||||
|
||||
protected $maximumRows;
|
||||
|
||||
const COUNTRY_FIELD = 'location_country';
|
||||
|
||||
const REGION_FIELD = 'location_region';
|
||||
|
||||
const CITY_FIELD = 'location_city';
|
||||
|
||||
protected $dimensions = array(self::COUNTRY_FIELD, self::REGION_FIELD, self::CITY_FIELD);
|
||||
|
||||
/**
|
||||
* @var DataArray[] $arrays
|
||||
*/
|
||||
protected $arrays;
|
||||
const LATITUDE_FIELD = 'location_latitude';
|
||||
const LONGITUDE_FIELD = 'location_longitude';
|
||||
|
||||
public function aggregateDayReport()
|
||||
{
|
||||
foreach ($this->dimensions as $dimension) {
|
||||
$this->arrays[$dimension] = new DataArray();
|
||||
}
|
||||
$this->aggregateFromVisits();
|
||||
$this->aggregateFromConversions();
|
||||
$this->insertDayReports();
|
||||
}
|
||||
|
||||
public function aggregateMultipleReports()
|
||||
{
|
||||
$dataTableToSum = array(
|
||||
self::COUNTRY_RECORD_NAME,
|
||||
self::REGION_RECORD_NAME,
|
||||
self::CITY_RECORD_NAME,
|
||||
);
|
||||
|
||||
$nameToCount = $this->getProcessor()->aggregateDataTableRecords($dataTableToSum);
|
||||
$this->getProcessor()->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC,
|
||||
$nameToCount[self::COUNTRY_RECORD_NAME]['level0']);
|
||||
}
|
||||
|
||||
protected function aggregateFromVisits()
|
||||
{
|
||||
$additionalSelects = array('MAX(log_visit.location_latitude) as location_latitude',
|
||||
'MAX(log_visit.location_longitude) as location_longitude');
|
||||
$query = $this->getLogAggregator()->queryVisitsByDimension($this->dimensions, $where = false, $additionalSelects);
|
||||
if ($query === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
while ($row = $query->fetch()) {
|
||||
$this->makeRegionCityLabelsUnique($row);
|
||||
$this->rememberCityLatLong($row);
|
||||
|
||||
foreach ($this->arrays as $dimension => $dataArray) {
|
||||
$dataArray->sumMetricsVisits($row[$dimension], $row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the region and city of a query row are unique.
|
||||
*
|
||||
* @param array $row
|
||||
*/
|
||||
private function makeRegionCityLabelsUnique(&$row)
|
||||
{
|
||||
// remove the location separator from the region/city/country we get from the query
|
||||
foreach ($this->dimensions as $column) {
|
||||
$row[$column] = str_replace(self::LOCATION_SEPARATOR, '', $row[$column]);
|
||||
}
|
||||
|
||||
if (!empty($row[self::REGION_FIELD])) {
|
||||
$row[self::REGION_FIELD] = $row[self::REGION_FIELD] . self::LOCATION_SEPARATOR . $row[self::COUNTRY_FIELD];
|
||||
}
|
||||
|
||||
if (!empty($row[self::CITY_FIELD])) {
|
||||
$row[self::CITY_FIELD] = $row[self::CITY_FIELD] . self::LOCATION_SEPARATOR . $row[self::REGION_FIELD];
|
||||
}
|
||||
}
|
||||
|
||||
protected function rememberCityLatLong($row)
|
||||
{
|
||||
if (!empty($row[self::CITY_FIELD])
|
||||
&& !empty($row[self::LATITUDE_FIELD])
|
||||
&& !empty($row[self::LONGITUDE_FIELD])
|
||||
&& empty($this->latLongForCities[$row[self::CITY_FIELD]])
|
||||
) {
|
||||
$this->latLongForCities[$row[self::CITY_FIELD]] = array($row[self::LATITUDE_FIELD], $row[self::LONGITUDE_FIELD]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function aggregateFromConversions()
|
||||
{
|
||||
$query = $this->getLogAggregator()->queryConversionsByDimension($this->dimensions);
|
||||
|
||||
if ($query === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
while ($row = $query->fetch()) {
|
||||
$this->makeRegionCityLabelsUnique($row);
|
||||
|
||||
foreach ($this->arrays as $dimension => $dataArray) {
|
||||
$dataArray->sumMetricsGoals($row[$dimension], $row);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->arrays as $dataArray) {
|
||||
$dataArray->enrichMetricsWithConversions();
|
||||
}
|
||||
}
|
||||
|
||||
protected function insertDayReports()
|
||||
{
|
||||
$tableCountry = $this->arrays[self::COUNTRY_FIELD]->asDataTable();
|
||||
$this->getProcessor()->insertNumericRecord(self::DISTINCT_COUNTRIES_METRIC, $tableCountry->getRowsCount());
|
||||
$report = $tableCountry->getSerialized();
|
||||
$this->getProcessor()->insertBlobRecord(self::COUNTRY_RECORD_NAME, $report);
|
||||
|
||||
$tableRegion = $this->arrays[self::REGION_FIELD]->asDataTable();
|
||||
$report = $tableRegion->getSerialized($this->maximumRows, $this->maximumRows, Metrics::INDEX_NB_VISITS);
|
||||
$this->getProcessor()->insertBlobRecord(self::REGION_RECORD_NAME, $report);
|
||||
|
||||
$tableCity = $this->arrays[self::CITY_FIELD]->asDataTable();
|
||||
$this->setLatitudeLongitude($tableCity);
|
||||
$report = $tableCity->getSerialized($this->maximumRows, $this->maximumRows, Metrics::INDEX_NB_VISITS);
|
||||
$this->getProcessor()->insertBlobRecord(self::CITY_RECORD_NAME, $report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method, appends latitude/longitude pairs to city table labels, if that data
|
||||
* exists for the city.
|
||||
*/
|
||||
private function setLatitudeLongitude(DataTable $tableCity)
|
||||
{
|
||||
foreach ($tableCity->getRows() as $row) {
|
||||
$label = $row->getColumn('label');
|
||||
if (isset($this->latLongForCities[$label])) {
|
||||
// get lat/long for city
|
||||
list($lat, $long) = $this->latLongForCities[$label];
|
||||
$lat = round($lat, LocationProvider::GEOGRAPHIC_COORD_PRECISION);
|
||||
$long = round($long, LocationProvider::GEOGRAPHIC_COORD_PRECISION);
|
||||
|
||||
// set latitude + longitude metadata
|
||||
$row->setMetadata('lat', $lat);
|
||||
$row->setMetadata('long', $long);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
403
www/analytics/plugins/UserCountry/Controller.php
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\UserCountry;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\DataTable\Renderer\Json;
|
||||
use Piwik\Http;
|
||||
use Piwik\IP;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp\Pecl;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp\ServerBased;
|
||||
use Piwik\View;
|
||||
use Piwik\ViewDataTable\Factory;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Controller extends \Piwik\Plugin\ControllerAdmin
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$view = new View('@UserCountry/index');
|
||||
|
||||
$view->urlSparklineCountries = $this->getUrlSparkline('getLastDistinctCountriesGraph');
|
||||
$view->numberDistinctCountries = $this->getNumberOfDistinctCountries(true);
|
||||
|
||||
$view->dataTableCountry = $this->getCountry(true);
|
||||
$view->dataTableContinent = $this->getContinent(true);
|
||||
$view->dataTableRegion = $this->getRegion(true);
|
||||
$view->dataTableCity = $this->getCity(true);
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
public function adminIndex()
|
||||
{
|
||||
$this->dieIfGeolocationAdminIsDisabled();
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
$view = new View('@UserCountry/adminIndex');
|
||||
|
||||
$allProviderInfo = LocationProvider::getAllProviderInfo($newline = '<br/>', $includeExtra = true);
|
||||
$view->locationProviders = $allProviderInfo;
|
||||
$view->currentProviderId = LocationProvider::getCurrentProviderId();
|
||||
$view->thisIP = IP::getIpFromHeader();
|
||||
$geoIPDatabasesInstalled = GeoIp::isDatabaseInstalled();
|
||||
$view->geoIPDatabasesInstalled = $geoIPDatabasesInstalled;
|
||||
|
||||
// check if there is a working provider (that isn't the default one)
|
||||
$isThereWorkingProvider = false;
|
||||
foreach ($allProviderInfo as $id => $provider) {
|
||||
if ($id != DefaultProvider::ID
|
||||
&& $provider['status'] == LocationProvider::INSTALLED
|
||||
) {
|
||||
$isThereWorkingProvider = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$view->isThereWorkingProvider = $isThereWorkingProvider;
|
||||
|
||||
// if using either the Apache or PECL module, they are working and there are no databases
|
||||
// in misc, then the databases are located outside of Piwik, so we cannot update them
|
||||
$view->showGeoIPUpdateSection = true;
|
||||
$currentProviderId = LocationProvider::getCurrentProviderId();
|
||||
if (!$geoIPDatabasesInstalled
|
||||
&& ($currentProviderId == ServerBased::ID
|
||||
|| $currentProviderId == Pecl::ID)
|
||||
&& $allProviderInfo[$currentProviderId]['status'] == LocationProvider::INSTALLED
|
||||
) {
|
||||
$view->showGeoIPUpdateSection = false;
|
||||
}
|
||||
|
||||
$this->setUpdaterManageVars($view);
|
||||
$this->setBasicVariablesView($view);
|
||||
$this->setBasicVariablesAdminView($view);
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or continues download of GeoLiteCity.dat.
|
||||
*
|
||||
* To avoid a server/PHP timeout & to show progress of the download to the user, we
|
||||
* use the HTTP Range header to download one chunk of the file at a time. After each
|
||||
* chunk, it is the browser's responsibility to call the method again to continue the download.
|
||||
*
|
||||
* Input:
|
||||
* 'continue' query param - if set to 1, will assume we are currently downloading & use
|
||||
* Range: HTTP header to get another chunk of the file.
|
||||
*
|
||||
* Output (in JSON):
|
||||
* 'current_size' - Current size of the partially downloaded file on disk.
|
||||
* 'expected_file_size' - The expected finished file size as returned by the HTTP server.
|
||||
* 'next_screen' - When the download finishes, this is the next screen that should be shown.
|
||||
* 'error' - When an error occurs, the message is returned in this property.
|
||||
*/
|
||||
public function downloadFreeGeoIPDB()
|
||||
{
|
||||
$this->dieIfGeolocationAdminIsDisabled();
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$this->checkTokenInUrl();
|
||||
Json::sendHeaderJSON();
|
||||
$outputPath = GeoIp::getPathForGeoIpDatabase('GeoIPCity.dat') . '.gz';
|
||||
try {
|
||||
$result = Http::downloadChunk(
|
||||
$url = GeoIp::GEO_LITE_URL,
|
||||
$outputPath,
|
||||
$continue = Common::getRequestVar('continue', true, 'int')
|
||||
);
|
||||
|
||||
// if the file is done
|
||||
if ($result['current_size'] >= $result['expected_file_size']) {
|
||||
GeoIPAutoUpdater::unzipDownloadedFile($outputPath, $unlink = true);
|
||||
|
||||
// setup the auto updater
|
||||
GeoIPAutoUpdater::setUpdaterOptions(array(
|
||||
'loc_db' => GeoIp::GEO_LITE_URL,
|
||||
'period' => GeoIPAutoUpdater::SCHEDULE_PERIOD_MONTHLY,
|
||||
));
|
||||
|
||||
// make sure to echo out the geoip updater management screen
|
||||
$result['next_screen'] = $this->getGeoIpUpdaterManageScreen();
|
||||
}
|
||||
|
||||
return Common::json_encode($result);
|
||||
} catch (Exception $ex) {
|
||||
return Common::json_encode(array('error' => $ex->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders and returns the HTML that manages the GeoIP auto-updater.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getGeoIpUpdaterManageScreen()
|
||||
{
|
||||
$view = new View('@UserCountry/getGeoIpUpdaterManageScreen');
|
||||
$view->geoIPDatabasesInstalled = true;
|
||||
$this->setUpdaterManageVars($view);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets some variables needed by the _updaterManage.twig template.
|
||||
*
|
||||
* @param View $view
|
||||
*/
|
||||
private function setUpdaterManageVars($view)
|
||||
{
|
||||
$urls = GeoIPAutoUpdater::getConfiguredUrls();
|
||||
|
||||
$view->geoIPLocUrl = $urls['loc'];
|
||||
$view->geoIPIspUrl = $urls['isp'];
|
||||
$view->geoIPOrgUrl = $urls['org'];
|
||||
$view->geoIPUpdatePeriod = GeoIPAutoUpdater::getSchedulePeriod();
|
||||
|
||||
$view->geoLiteUrl = GeoIp::GEO_LITE_URL;
|
||||
|
||||
$lastRunTime = GeoIPAutoUpdater::getLastRunTime();
|
||||
if ($lastRunTime !== false) {
|
||||
$view->lastTimeUpdaterRun = '<strong><em>' . $lastRunTime->toString() . '</em></strong>';
|
||||
}
|
||||
|
||||
$view->nextRunTime = GeoIPAutoUpdater::getNextRunTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URLs used to download new versions of the installed GeoIP databases.
|
||||
*
|
||||
* Input (query params):
|
||||
* 'loc_db' - URL for a GeoIP location database.
|
||||
* 'isp_db' - URL for a GeoIP ISP database (optional).
|
||||
* 'org_db' - URL for a GeoIP Org database (optional).
|
||||
* 'period' - 'weekly' or 'monthly'. Determines how often update is run.
|
||||
*
|
||||
* Output (json):
|
||||
* 'error' - if an error occurs its message is set as the resulting JSON object's
|
||||
* 'error' property.
|
||||
*/
|
||||
public function updateGeoIPLinks()
|
||||
{
|
||||
$this->dieIfGeolocationAdminIsDisabled();
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
Json::sendHeaderJSON();
|
||||
try {
|
||||
$this->checkTokenInUrl();
|
||||
|
||||
GeoIPAutoUpdater::setUpdaterOptionsFromUrl();
|
||||
|
||||
// if there is a updater URL for a database, but its missing from the misc dir, tell
|
||||
// the browser so it can download it next
|
||||
$info = $this->getNextMissingDbUrlInfo();
|
||||
if ($info !== false) {
|
||||
return Common::json_encode($info);
|
||||
} else {
|
||||
$view = new View("@UserCountry/_updaterNextRunTime");
|
||||
$view->nextRunTime = GeoIPAutoUpdater::getNextRunTime();
|
||||
$nextRunTimeHtml = $view->render();
|
||||
return Common::json_encode(array('nextRunTime' => $nextRunTimeHtml));
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
return Common::json_encode(array('error' => $ex->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or continues a download for a missing GeoIP database. A database is missing if
|
||||
* it has an update URL configured, but the actual database is not available in the misc
|
||||
* directory.
|
||||
*
|
||||
* Input:
|
||||
* 'url' - The URL to download the database from.
|
||||
* 'continue' - 1 if we're continuing a download, 0 if we're starting one.
|
||||
*
|
||||
* Output:
|
||||
* 'error' - If an error occurs this describes the error.
|
||||
* 'to_download' - The URL of a missing database that should be downloaded next (if any).
|
||||
* 'to_download_label' - The label to use w/ the progress bar that describes what we're
|
||||
* downloading.
|
||||
* 'current_size' - Size of the current file on disk.
|
||||
* 'expected_file_size' - Size of the completely downloaded file.
|
||||
*/
|
||||
public function downloadMissingGeoIpDb()
|
||||
{
|
||||
$this->dieIfGeolocationAdminIsDisabled();
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
try {
|
||||
$this->checkTokenInUrl();
|
||||
|
||||
Json::sendHeaderJSON();
|
||||
|
||||
// based on the database type (provided by the 'key' query param) determine the
|
||||
// url & output file name
|
||||
$key = Common::getRequestVar('key', null, 'string');
|
||||
$url = GeoIPAutoUpdater::getConfiguredUrl($key);
|
||||
|
||||
$ext = GeoIPAutoUpdater::getGeoIPUrlExtension($url);
|
||||
$filename = GeoIp::$dbNames[$key][0] . '.' . $ext;
|
||||
|
||||
if (substr($filename, 0, 15) == 'GeoLiteCity.dat') {
|
||||
$filename = 'GeoIPCity.dat' . substr($filename, 15);
|
||||
}
|
||||
$outputPath = GeoIp::getPathForGeoIpDatabase($filename);
|
||||
|
||||
// download part of the file
|
||||
$result = Http::downloadChunk(
|
||||
$url, $outputPath, Common::getRequestVar('continue', true, 'int'));
|
||||
|
||||
// if the file is done
|
||||
if ($result['current_size'] >= $result['expected_file_size']) {
|
||||
GeoIPAutoUpdater::unzipDownloadedFile($outputPath, $unlink = true);
|
||||
|
||||
$info = $this->getNextMissingDbUrlInfo();
|
||||
if ($info !== false) {
|
||||
return Common::json_encode($info);
|
||||
}
|
||||
}
|
||||
|
||||
return Common::json_encode($result);
|
||||
} catch (Exception $ex) {
|
||||
return Common::json_encode(array('error' => $ex->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current LocationProvider type.
|
||||
*
|
||||
* Input:
|
||||
* Requires the 'id' query parameter to be set to the desired LocationProvider's ID.
|
||||
*
|
||||
* Output:
|
||||
* Nothing.
|
||||
*/
|
||||
public function setCurrentLocationProvider()
|
||||
{
|
||||
$this->dieIfGeolocationAdminIsDisabled();
|
||||
Piwik::checkUserHasSuperUserAccess();
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$this->checkTokenInUrl();
|
||||
|
||||
$providerId = Common::getRequestVar('id');
|
||||
$provider = LocationProvider::setCurrentProvider($providerId);
|
||||
if ($provider === false) {
|
||||
throw new Exception("Invalid provider ID: '$providerId'.");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Echo's a pretty formatted location using a specific LocationProvider.
|
||||
*
|
||||
* Input:
|
||||
* The 'id' query parameter must be set to the ID of the LocationProvider to use.
|
||||
*
|
||||
* Output:
|
||||
* The pretty formatted location that was obtained. Will be HTML.
|
||||
*/
|
||||
public function getLocationUsingProvider()
|
||||
{
|
||||
$providerId = Common::getRequestVar('id');
|
||||
$provider = $provider = LocationProvider::getProviderById($providerId);
|
||||
if ($provider === false) {
|
||||
throw new Exception("Invalid provider ID: '$providerId'.");
|
||||
}
|
||||
|
||||
$location = $provider->getLocation(array('ip' => IP::getIpFromHeader(),
|
||||
'lang' => Common::getBrowserLanguage(),
|
||||
'disable_fallbacks' => true));
|
||||
$location = LocationProvider::prettyFormatLocation(
|
||||
$location, $newline = '<br/>', $includeExtra = true);
|
||||
|
||||
return $location;
|
||||
}
|
||||
|
||||
public function getCountry()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getContinent()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Echo's or returns an HTML view of the visits by region report.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRegion()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Echo's or returns an HTML view of the visits by city report.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCity()
|
||||
{
|
||||
return $this->renderReport(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getNumberOfDistinctCountries()
|
||||
{
|
||||
return $this->getNumericValue('UserCountry.getNumberOfDistinctCountries');
|
||||
}
|
||||
|
||||
public function getLastDistinctCountriesGraph()
|
||||
{
|
||||
$view = $this->getLastUnitGraph('UserCountry', __FUNCTION__, "UserCountry.getNumberOfDistinctCountries");
|
||||
$view->config->columns_to_display = array('UserCountry_distinctCountries');
|
||||
return $this->renderView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information for the first missing GeoIP database (if any).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function getNextMissingDbUrlInfo()
|
||||
{
|
||||
$missingDbs = GeoIPAutoUpdater::getMissingDatabases();
|
||||
if (!empty($missingDbs)) {
|
||||
$missingDbKey = $missingDbs[0];
|
||||
$missingDbName = GeoIp::$dbNames[$missingDbKey][0];
|
||||
$url = GeoIPAutoUpdater::getConfiguredUrl($missingDbKey);
|
||||
|
||||
$link = '<a href="' . $url . '">' . $missingDbName . '</a>';
|
||||
|
||||
return array(
|
||||
'to_download' => $missingDbKey,
|
||||
'to_download_label' => Piwik::translate('UserCountry_DownloadingDb', $link) . '...',
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function dieIfGeolocationAdminIsDisabled()
|
||||
{
|
||||
if(!UserCountry::isGeoLocationAdminEnabled()) {
|
||||
throw new \Exception('Geo location setting page has been disabled.');
|
||||
}
|
||||
}
|
||||
}
|
||||
682
www/analytics/plugins/UserCountry/GeoIPAutoUpdater.php
Executable file
|
|
@ -0,0 +1,682 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\UserCountry;
|
||||
|
||||
require_once PIWIK_INCLUDE_PATH . "/core/ScheduledTask.php"; // for the tracker which doesn't include this file
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\Date;
|
||||
use Piwik\Http;
|
||||
use Piwik\Log;
|
||||
use Piwik\Option;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp\Php;
|
||||
use Piwik\ScheduledTask;
|
||||
use Piwik\ScheduledTaskTimetable;
|
||||
use Piwik\ScheduledTime\Monthly;
|
||||
use Piwik\ScheduledTime\Weekly;
|
||||
use Piwik\TaskScheduler;
|
||||
use Piwik\Unzip;
|
||||
|
||||
/**
|
||||
* Used to automatically update installed GeoIP databases, and manages the updater's
|
||||
* scheduled task.
|
||||
*/
|
||||
class GeoIPAutoUpdater extends ScheduledTask
|
||||
{
|
||||
const SCHEDULE_PERIOD_MONTHLY = 'month';
|
||||
const SCHEDULE_PERIOD_WEEKLY = 'week';
|
||||
|
||||
const SCHEDULE_PERIOD_OPTION_NAME = 'geoip.updater_period';
|
||||
const LOC_URL_OPTION_NAME = 'geoip.loc_db_url';
|
||||
const ISP_URL_OPTION_NAME = 'geoip.isp_db_url';
|
||||
const ORG_URL_OPTION_NAME = 'geoip.org_db_url';
|
||||
|
||||
const LAST_RUN_TIME_OPTION_NAME = 'geoip.updater_last_run_time';
|
||||
|
||||
private static $urlOptions = array(
|
||||
'loc' => self::LOC_URL_OPTION_NAME,
|
||||
'isp' => self::ISP_URL_OPTION_NAME,
|
||||
'org' => self::ORG_URL_OPTION_NAME,
|
||||
);
|
||||
|
||||
/**
|
||||
* PHP Error caught through a custom error handler while trying to use a downloaded
|
||||
* GeoIP database. See catchGeoIPError for more info.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $unzipPhpError = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$schedulePeriodStr = self::getSchedulePeriod();
|
||||
|
||||
// created the scheduledtime instance, also, since GeoIP updates are done on tuesdays,
|
||||
// get new DBs on Wednesday
|
||||
switch ($schedulePeriodStr) {
|
||||
case self::SCHEDULE_PERIOD_WEEKLY:
|
||||
$schedulePeriod = new Weekly();
|
||||
$schedulePeriod->setDay(3);
|
||||
break;
|
||||
case self::SCHEDULE_PERIOD_MONTHLY:
|
||||
default:
|
||||
$schedulePeriod = new Monthly();
|
||||
$schedulePeriod->setDayOfWeek(3, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
parent::__construct($this, 'update', null, $schedulePeriod, ScheduledTask::LOWEST_PRIORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to download new location, ISP & organization GeoIP databases and
|
||||
* replace the existing ones w/ them.
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
try {
|
||||
Option::set(self::LAST_RUN_TIME_OPTION_NAME, Date::factory('today')->getTimestamp());
|
||||
|
||||
$locUrl = Option::get(self::LOC_URL_OPTION_NAME);
|
||||
if (!empty($locUrl)) {
|
||||
$this->downloadFile('loc', $locUrl);
|
||||
}
|
||||
|
||||
$ispUrl = Option::get(self::ISP_URL_OPTION_NAME);
|
||||
if (!empty($ispUrl)) {
|
||||
$this->downloadFile('isp', $ispUrl);
|
||||
}
|
||||
|
||||
$orgUrl = Option::get(self::ORG_URL_OPTION_NAME);
|
||||
if (!empty($orgUrl)) {
|
||||
$this->downloadFile('org', $orgUrl);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
// message will already be prefixed w/ 'GeoIPAutoUpdater: '
|
||||
Log::error($ex);
|
||||
$this->performRedundantDbChecks();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$this->performRedundantDbChecks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a GeoIP database archive, extracts the .dat file and overwrites the existing
|
||||
* old database.
|
||||
*
|
||||
* If something happens that causes the download to fail, no exception is thrown, but
|
||||
* an error is logged.
|
||||
*
|
||||
* @param string $dbType
|
||||
* @param string $url URL to the database to download. The type of database is determined
|
||||
* from this URL.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function downloadFile($dbType, $url)
|
||||
{
|
||||
$ext = GeoIPAutoUpdater::getGeoIPUrlExtension($url);
|
||||
|
||||
// NOTE: using the first item in $dbNames[$dbType] makes sure GeoLiteCity will be renamed to GeoIPCity
|
||||
$zippedFilename = GeoIp::$dbNames[$dbType][0] . '.' . $ext;
|
||||
|
||||
$zippedOutputPath = GeoIp::getPathForGeoIpDatabase($zippedFilename);
|
||||
|
||||
$url = self::removeDateFromUrl($url);
|
||||
|
||||
// download zipped file to misc dir
|
||||
try {
|
||||
$success = Http::sendHttpRequest($url, $timeout = 3600, $userAgent = null, $zippedOutputPath);
|
||||
} catch (Exception $ex) {
|
||||
throw new Exception("GeoIPAutoUpdater: failed to download '$url' to "
|
||||
. "'$zippedOutputPath': " . $ex->getMessage());
|
||||
}
|
||||
|
||||
if ($success !== true) {
|
||||
throw new Exception("GeoIPAutoUpdater: failed to download '$url' to "
|
||||
. "'$zippedOutputPath'! (Unknown error)");
|
||||
}
|
||||
|
||||
Log::info("GeoIPAutoUpdater: successfully downloaded '%s'", $url);
|
||||
|
||||
try {
|
||||
self::unzipDownloadedFile($zippedOutputPath, $unlink = true);
|
||||
} catch (Exception $ex) {
|
||||
throw new Exception("GeoIPAutoUpdater: failed to unzip '$zippedOutputPath' after "
|
||||
. "downloading " . "'$url': " . $ex->getMessage());
|
||||
}
|
||||
|
||||
Log::info("GeoIPAutoUpdater: successfully updated GeoIP database '%s'", $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzips a downloaded GeoIP database. Only unzips .gz & .tar.gz files.
|
||||
*
|
||||
* @param string $path Path to zipped file.
|
||||
* @param bool $unlink Whether to unlink archive or not.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function unzipDownloadedFile($path, $unlink = false)
|
||||
{
|
||||
$parts = explode('.', basename($path));
|
||||
$filenameStart = $parts[0];
|
||||
|
||||
$dbFilename = $filenameStart . '.dat';
|
||||
$tempFilename = $filenameStart . '.dat.new';
|
||||
$outputPath = GeoIp::getPathForGeoIpDatabase($tempFilename);
|
||||
|
||||
// extract file
|
||||
if (substr($path, -7, 7) == '.tar.gz') {
|
||||
// find the .dat file in the tar archive
|
||||
$unzip = Unzip::factory('tar.gz', $path);
|
||||
$content = $unzip->listContent();
|
||||
|
||||
if (empty($content)) {
|
||||
throw new Exception(Piwik::translate('UserCountry_CannotListContent',
|
||||
array("'$path'", $unzip->errorInfo())));
|
||||
}
|
||||
|
||||
$datFile = null;
|
||||
foreach ($content as $info) {
|
||||
$archivedPath = $info['filename'];
|
||||
if (basename($archivedPath) === $dbFilename) {
|
||||
$datFile = $archivedPath;
|
||||
}
|
||||
}
|
||||
|
||||
if ($datFile === null) {
|
||||
throw new Exception(Piwik::translate('UserCountry_CannotFindGeoIPDatabaseInArchive',
|
||||
array($dbFilename, "'$path'")));
|
||||
}
|
||||
|
||||
// extract JUST the .dat file
|
||||
$unzipped = $unzip->extractInString($datFile);
|
||||
|
||||
if (empty($unzipped)) {
|
||||
throw new Exception(Piwik::translate('UserCountry_CannotUnzipDatFile',
|
||||
array("'$path'", $unzip->errorInfo())));
|
||||
}
|
||||
|
||||
// write unzipped to file
|
||||
$fd = fopen($outputPath, 'wb');
|
||||
fwrite($fd, $unzipped);
|
||||
fclose($fd);
|
||||
} else if (substr($path, -3, 3) == '.gz') {
|
||||
$unzip = Unzip::factory('gz', $path);
|
||||
$success = $unzip->extract($outputPath);
|
||||
|
||||
if ($success !== true) {
|
||||
throw new Exception(Piwik::translate('UserCountry_CannotUnzipDatFile',
|
||||
array("'$path'", $unzip->errorInfo())));
|
||||
}
|
||||
} else {
|
||||
$ext = end(explode(basename($path), '.', 2));
|
||||
throw new Exception(Piwik::translate('UserCountry_UnsupportedArchiveType', "'$ext'"));
|
||||
}
|
||||
|
||||
try {
|
||||
// test that the new archive is a valid GeoIP database
|
||||
$dbType = GeoIp::getGeoIPDatabaseTypeFromFilename($dbFilename);
|
||||
if ($dbType === false) // sanity check
|
||||
{
|
||||
throw new Exception("Unexpected GeoIP archive file name '$path'.");
|
||||
}
|
||||
|
||||
$customDbNames = array(
|
||||
'loc' => array(),
|
||||
'isp' => array(),
|
||||
'org' => array()
|
||||
);
|
||||
$customDbNames[$dbType] = array($tempFilename);
|
||||
|
||||
$phpProvider = new Php($customDbNames);
|
||||
|
||||
$location = self::getTestLocationCatchPhpErrors($phpProvider);
|
||||
|
||||
if (empty($location)
|
||||
|| self::$unzipPhpError !== null
|
||||
) {
|
||||
if (self::$unzipPhpError !== null) {
|
||||
list($errno, $errstr, $errfile, $errline) = self::$unzipPhpError;
|
||||
Log::info("GeoIPAutoUpdater: Encountered PHP error when testing newly downloaded" .
|
||||
" GeoIP database: %s: %s on line %s of %s.", $errno, $errstr, $errline, $errfile);
|
||||
}
|
||||
|
||||
throw new Exception(Piwik::translate('UserCountry_ThisUrlIsNotAValidGeoIPDB'));
|
||||
}
|
||||
|
||||
// delete the existing GeoIP database (if any) and rename the downloaded file
|
||||
$oldDbFile = GeoIp::getPathForGeoIpDatabase($dbFilename);
|
||||
if (file_exists($oldDbFile)) {
|
||||
unlink($oldDbFile);
|
||||
}
|
||||
|
||||
$tempFile = GeoIp::getPathForGeoIpDatabase($tempFilename);
|
||||
rename($existing = $tempFile, $newName = $oldDbFile);
|
||||
|
||||
// delete original archive
|
||||
if ($unlink) {
|
||||
unlink($path);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
// remove downloaded files
|
||||
if (file_exists($outputPath)) {
|
||||
unlink($outputPath);
|
||||
}
|
||||
unlink($path);
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options used by this class based on query parameter values.
|
||||
*
|
||||
* See setUpdaterOptions for query params used.
|
||||
*/
|
||||
public static function setUpdaterOptionsFromUrl()
|
||||
{
|
||||
$options = array(
|
||||
'loc' => Common::getRequestVar('loc_db', false, 'string'),
|
||||
'isp' => Common::getRequestVar('isp_db', false, 'string'),
|
||||
'org' => Common::getRequestVar('org_db', false, 'string'),
|
||||
'period' => Common::getRequestVar('period', false, 'string'),
|
||||
);
|
||||
|
||||
foreach (self::$urlOptions as $optionKey => $optionName) {
|
||||
$options[$optionKey] = Common::unsanitizeInputValue($options[$optionKey]); // URLs should not be sanitized
|
||||
}
|
||||
|
||||
self::setUpdaterOptions($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options used by this class based on the elements in $options.
|
||||
*
|
||||
* The following elements of $options are used:
|
||||
* 'loc' - URL for location database.
|
||||
* 'isp' - URL for ISP database.
|
||||
* 'org' - URL for Organization database.
|
||||
* 'period' - 'weekly' or 'monthly'. When to run the updates.
|
||||
*
|
||||
* @param array $options
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function setUpdaterOptions($options)
|
||||
{
|
||||
// set url options
|
||||
foreach (self::$urlOptions as $optionKey => $optionName) {
|
||||
if (!isset($options[$optionKey])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $options[$optionKey];
|
||||
$url = self::removeDateFromUrl($url);
|
||||
|
||||
Option::set($optionName, $url);
|
||||
}
|
||||
|
||||
// set period option
|
||||
if (!empty($options['period'])) {
|
||||
$period = $options['period'];
|
||||
|
||||
if ($period != self::SCHEDULE_PERIOD_MONTHLY
|
||||
&& $period != self::SCHEDULE_PERIOD_WEEKLY
|
||||
) {
|
||||
throw new Exception(Piwik::translate(
|
||||
'UserCountry_InvalidGeoIPUpdatePeriod',
|
||||
array("'$period'", "'" . self::SCHEDULE_PERIOD_MONTHLY . "', '" . self::SCHEDULE_PERIOD_WEEKLY . "'")
|
||||
));
|
||||
}
|
||||
|
||||
Option::set(self::SCHEDULE_PERIOD_OPTION_NAME, $period);
|
||||
|
||||
TaskScheduler::rescheduleTask(new GeoIPAutoUpdater());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the auto-updater is setup to update at least one type of
|
||||
* database. False if otherwise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUpdaterSetup()
|
||||
{
|
||||
if (Option::get(self::LOC_URL_OPTION_NAME) !== false
|
||||
|| Option::get(self::ISP_URL_OPTION_NAME) !== false
|
||||
|| Option::get(self::ORG_URL_OPTION_NAME) !== false
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the URLs used to update various GeoIP database files.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getConfiguredUrls()
|
||||
{
|
||||
$result = array();
|
||||
foreach (self::$urlOptions as $key => $optionName) {
|
||||
$result[$key] = Option::get($optionName);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the confiured URL (if any) for a type of database.
|
||||
*
|
||||
* @param string $key 'loc', 'isp' or 'org'
|
||||
* @throws Exception
|
||||
* @return string|false
|
||||
*/
|
||||
public static function getConfiguredUrl($key)
|
||||
{
|
||||
if (empty(self::$urlOptions[$key])) {
|
||||
throw new Exception("Invalid key $key");
|
||||
}
|
||||
$url = Option::get(self::$urlOptions[$key]);
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a GeoIP database update.
|
||||
*/
|
||||
public static function performUpdate()
|
||||
{
|
||||
$instance = new GeoIPAutoUpdater();
|
||||
$instance->update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured update period, either 'week' or 'month'. Defaults to
|
||||
* 'month'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSchedulePeriod()
|
||||
{
|
||||
$period = Option::get(self::SCHEDULE_PERIOD_OPTION_NAME);
|
||||
if ($period === false) {
|
||||
$period = self::SCHEDULE_PERIOD_MONTHLY;
|
||||
}
|
||||
return $period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of strings for GeoIP databases that have update URLs configured, but
|
||||
* are not present in the misc directory. Each string is a key describing the type of
|
||||
* database (ie, 'loc', 'isp' or 'org').
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getMissingDatabases()
|
||||
{
|
||||
$result = array();
|
||||
foreach (self::getConfiguredUrls() as $key => $url) {
|
||||
if (!empty($url)) {
|
||||
// if a database of the type does not exist, but there's a url to update, then
|
||||
// a database is missing
|
||||
$path = GeoIp::getPathToGeoIpDatabase(
|
||||
GeoIp::$dbNames[$key]);
|
||||
if ($path === false) {
|
||||
$result[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension of a URL used to update a GeoIP database, if it can be found.
|
||||
*/
|
||||
public static function getGeoIPUrlExtension($url)
|
||||
{
|
||||
// check for &suffix= query param that is special to MaxMind URLs
|
||||
if (preg_match('/suffix=([^&]+)/', $url, $matches)) {
|
||||
$ext = $matches[1];
|
||||
} else {
|
||||
// use basename of url
|
||||
$filenameParts = explode('.', basename($url), 2);
|
||||
if (count($filenameParts) > 1) {
|
||||
$ext = end($filenameParts);
|
||||
} else {
|
||||
$ext = reset($filenameParts);
|
||||
}
|
||||
}
|
||||
|
||||
self::checkForSupportedArchiveType($ext);
|
||||
|
||||
return $ext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid downloading archive types we don't support. No point in downloading it,
|
||||
* if we can't unzip it...
|
||||
*
|
||||
* @param string $ext The URL file's extension.
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function checkForSupportedArchiveType($ext)
|
||||
{
|
||||
if ($ext != 'tar.gz'
|
||||
&& $ext != 'gz'
|
||||
&& $ext != 'dat.gz'
|
||||
) {
|
||||
throw new \Exception(Piwik::translate('UserCountry_UnsupportedArchiveType', "'$ext'"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a location provider using a test IP address and catches PHP errors
|
||||
* (ie, notices) if they occur. PHP error information is held in self::$unzipPhpError.
|
||||
*
|
||||
* @param LocationProvider $provider The provider to test.
|
||||
* @return array|false $location The result of geolocation. False if no location
|
||||
* can be found.
|
||||
*/
|
||||
private static function getTestLocationCatchPhpErrors($provider)
|
||||
{
|
||||
// note: in most cases where this will fail, the error will usually be a PHP fatal error/notice.
|
||||
// in order to delete the files in such a case (which can be caused by a man-in-the-middle attack)
|
||||
// we need to catch them, so we set a new error handler.
|
||||
self::$unzipPhpError = null;
|
||||
set_error_handler(array('Piwik\Plugins\UserCountry\GeoIPAutoUpdater', 'catchGeoIPError'));
|
||||
|
||||
$location = $provider->getLocation(array('ip' => GeoIp::TEST_IP));
|
||||
|
||||
restore_error_handler();
|
||||
|
||||
return $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that checks if geolocation works with each installed database,
|
||||
* and if one or more doesn't, they are renamed to make sure tracking will work.
|
||||
* This is a safety measure used to make sure tracking isn't affected if strange
|
||||
* update errors occur.
|
||||
*
|
||||
* Databases are renamed to ${original}.broken .
|
||||
*
|
||||
* Note: method is protected for testability.
|
||||
*/
|
||||
protected function performRedundantDbChecks()
|
||||
{
|
||||
$databaseTypes = array_keys(GeoIp::$dbNames);
|
||||
|
||||
foreach ($databaseTypes as $type) {
|
||||
$customNames = array(
|
||||
'loc' => array(),
|
||||
'isp' => array(),
|
||||
'org' => array()
|
||||
);
|
||||
$customNames[$type] = GeoIp::$dbNames[$type];
|
||||
|
||||
// create provider that only uses the DB type we're testing
|
||||
$provider = new Php($customNames);
|
||||
|
||||
// test the provider. on error, we rename the broken DB.
|
||||
self::getTestLocationCatchPhpErrors($provider);
|
||||
if (self::$unzipPhpError !== null) {
|
||||
list($errno, $errstr, $errfile, $errline) = self::$unzipPhpError;
|
||||
Log::warning("GeoIPAutoUpdater: Encountered PHP error when performing redundant tests on GeoIP "
|
||||
. "%s database: %s: %s on line %s of %s.", $type, $errno, $errstr, $errline, $errfile);
|
||||
|
||||
// get the current filename for the DB and an available new one to rename it to
|
||||
list($oldPath, $newPath) = $this->getOldAndNewPathsForBrokenDb($customNames[$type]);
|
||||
|
||||
// rename the DB so tracking will not fail
|
||||
if ($oldPath !== false
|
||||
&& $newPath !== false
|
||||
) {
|
||||
if (file_exists($newPath)) {
|
||||
unlink($newPath);
|
||||
}
|
||||
|
||||
rename($oldPath, $newPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to a GeoIP database and a path to rename it to if it's broken.
|
||||
*
|
||||
* @param array $possibleDbNames The possible names of the database.
|
||||
* @return array Array with two elements, the path to the existing database, and
|
||||
* the path to rename it to if it is broken. The second will end
|
||||
* with something like .broken .
|
||||
*/
|
||||
private function getOldAndNewPathsForBrokenDb($possibleDbNames)
|
||||
{
|
||||
$pathToDb = GeoIp::getPathToGeoIpDatabase($possibleDbNames);
|
||||
$newPath = false;
|
||||
|
||||
if ($pathToDb !== false) {
|
||||
$newPath = $pathToDb . ".broken";
|
||||
}
|
||||
|
||||
return array($pathToDb, $newPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom PHP error handler used to catch any PHP errors that occur when
|
||||
* testing a downloaded GeoIP file.
|
||||
*
|
||||
* If we download a file that is supposed to be a GeoIP database, we need to make
|
||||
* sure it is one. This is done simply by attempting to use it. If this fails, it
|
||||
* will most of the time fail as a PHP error, which we catch w/ this function
|
||||
* after it is passed to set_error_handler.
|
||||
*
|
||||
* The PHP error is stored in self::$unzipPhpError.
|
||||
*
|
||||
* @param int $errno
|
||||
* @param string $errstr
|
||||
* @param string $errfile
|
||||
* @param int $errline
|
||||
*/
|
||||
public static function catchGeoIPError($errno, $errstr, $errfile, $errline)
|
||||
{
|
||||
self::$unzipPhpError = array($errno, $errstr, $errfile, $errline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time the auto updater was last run.
|
||||
*
|
||||
* @return Date|false
|
||||
*/
|
||||
public static function getLastRunTime()
|
||||
{
|
||||
$timestamp = Option::get(self::LAST_RUN_TIME_OPTION_NAME);
|
||||
return $timestamp === false ? false : Date::factory((int)$timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the &date=... query parameter if present in the URL. This query parameter
|
||||
* is in MaxMind URLs by default and will force the download of an old database.
|
||||
*
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
private static function removeDateFromUrl($url)
|
||||
{
|
||||
return preg_replace("/&date=[^&#]*/", '', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next scheduled time for the auto updater.
|
||||
*
|
||||
* @return Date|false
|
||||
*/
|
||||
public static function getNextRunTime()
|
||||
{
|
||||
$task = new GeoIPAutoUpdater();
|
||||
|
||||
$timetable = new ScheduledTaskTimetable();
|
||||
return $timetable->getScheduledTaskTime($task->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link Piwik\ScheduledTime::getRescheduledTime()}.
|
||||
*/
|
||||
public function getRescheduledTime()
|
||||
{
|
||||
$nextScheduledTime = parent::getRescheduledTime();
|
||||
|
||||
// if a geoip database is out of date, run the updater as soon as possible
|
||||
if ($this->isAtLeastOneGeoIpDbOutOfDate($nextScheduledTime)) {
|
||||
return time();
|
||||
}
|
||||
|
||||
return $nextScheduledTime;
|
||||
}
|
||||
|
||||
private function isAtLeastOneGeoIpDbOutOfDate($rescheduledTime)
|
||||
{
|
||||
$previousScheduledRuntime = $this->getPreviousScheduledTime($rescheduledTime)->setTime("00:00:00")->getTimestamp();
|
||||
|
||||
foreach (GeoIp::$dbNames as $type => $dbNames) {
|
||||
$dbUrl = Option::get(self::$urlOptions[$type]);
|
||||
$dbPath = GeoIp::getPathToGeoIpDatabase($dbNames);
|
||||
|
||||
// if there is a URL for this DB type and the GeoIP DB file's last modified time is before
|
||||
// the time the updater should have been previously run, then **the file is out of date**
|
||||
if (!empty($dbUrl)
|
||||
&& filemtime($dbPath) < $previousScheduledRuntime
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function getPreviousScheduledTime($rescheduledTime)
|
||||
{
|
||||
$updaterPeriod = self::getSchedulePeriod();
|
||||
|
||||
if ($updaterPeriod == self::SCHEDULE_PERIOD_WEEKLY) {
|
||||
return Date::factory($rescheduledTime)->subWeek(1);
|
||||
} else if ($updaterPeriod == self::SCHEDULE_PERIOD_MONTHLY) {
|
||||
return Date::factory($rescheduledTime)->subMonth(1);
|
||||
}
|
||||
throw new Exception("Unknown GeoIP updater period found in database: %s", $updaterPeriod);
|
||||
}
|
||||
}
|
||||
459
www/analytics/plugins/UserCountry/LocationProvider.php
Executable file
|
|
@ -0,0 +1,459 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\UserCountry;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\IP;
|
||||
use Piwik\Option;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider;
|
||||
use Piwik\Tracker\Cache;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* @see plugins/UserCountry/LocationProvider/Default.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/LocationProvider/Default.php';
|
||||
|
||||
/**
|
||||
* @see plugins/UserCountry/LocationProvider/GeoIp.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/LocationProvider/GeoIp.php';
|
||||
|
||||
/**
|
||||
* The base class of all LocationProviders.
|
||||
*
|
||||
* LocationProviders attempt to determine a visitor's location using other
|
||||
* visitor info. All LocationProviders require a visitor's IP address, some
|
||||
* require more, such as the browser language.
|
||||
*/
|
||||
abstract class LocationProvider
|
||||
{
|
||||
const NOT_INSTALLED = 0;
|
||||
const INSTALLED = 1;
|
||||
const BROKEN = 2;
|
||||
|
||||
const CURRENT_PROVIDER_OPTION_NAME = 'usercountry.location_provider';
|
||||
|
||||
const GEOGRAPHIC_COORD_PRECISION = 3;
|
||||
|
||||
const CONTINENT_CODE_KEY = 'continent_code';
|
||||
const CONTINENT_NAME_KEY = 'continent_name';
|
||||
const COUNTRY_CODE_KEY = 'country_code';
|
||||
const COUNTRY_NAME_KEY = 'country_name';
|
||||
const REGION_CODE_KEY = 'region_code';
|
||||
const REGION_NAME_KEY = 'region_name';
|
||||
const CITY_NAME_KEY = 'city_name';
|
||||
const AREA_CODE_KEY = 'area_code';
|
||||
const LATITUDE_KEY = 'lat';
|
||||
const LONGITUDE_KEY = 'long';
|
||||
const POSTAL_CODE_KEY = 'postal_code';
|
||||
const ISP_KEY = 'isp';
|
||||
const ORG_KEY = 'org';
|
||||
|
||||
/**
|
||||
* An array of all provider instances. Access it through static methods.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $providers = null;
|
||||
|
||||
/**
|
||||
* Returns location information based on visitor information.
|
||||
*
|
||||
* The result of this function will be an array. The array can store some or all of
|
||||
* the following information:
|
||||
*
|
||||
* - Continent Code: The code of the visitor's continent.
|
||||
* (array key is self::CONTINENT_CODE_KEY)
|
||||
* - Continent Name: The name of the visitor's continent.
|
||||
* (array key is self::CONTINENT_NAME_KEY)
|
||||
* - Country Code: The code of the visitor's country.
|
||||
* (array key is self::COUNTRY_CODE_KEY)
|
||||
* - Country Name: The name of the visitor's country.
|
||||
* (array key is self::COUNTRY_NAME_KEY)
|
||||
* - Region Code: The code of the visitor's region.
|
||||
* (array key is self::REGION_CODE_KEY)
|
||||
* - Region Name: The name of the visitor's region.
|
||||
* (array key is self::REGION_NAME_KEY)
|
||||
* - City Name: The name of the visitor's city.
|
||||
* (array key is self::CITY_NAME_KEY)
|
||||
* - Area Code: The visitor's area code.
|
||||
* (array key is self::AREA_CODE_KEY)
|
||||
* - Latitude: The visitor's latitude.
|
||||
* (array key is self::LATITUDE_KEY)
|
||||
* - Longitude: The visitor's longitude.
|
||||
* (array key is self::LONGITUDE_KEY)
|
||||
* - Postal Code: The visitor's postal code.
|
||||
* (array key is self::POSTAL_CODE_KEY)
|
||||
* - ISP: The visitor's ISP.
|
||||
* (array key is self::ISP_KEY)
|
||||
* - Org: The company/organization of the visitor's IP.
|
||||
* (array key is self::ORG_KEY)
|
||||
*
|
||||
* All LocationProviders will attempt to return the country of the visitor.
|
||||
*
|
||||
* @param array $info What this must contain depends on the specific provider
|
||||
* implementation. All providers require an 'ip' key mapped
|
||||
* to the visitor's IP address.
|
||||
* @return array|false
|
||||
*/
|
||||
abstract public function getLocation($info);
|
||||
|
||||
/**
|
||||
* Returns true if this provider is available for use, false if otherwise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isAvailable();
|
||||
|
||||
/**
|
||||
* Returns true if this provider is working, false if otherwise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isWorking();
|
||||
|
||||
/**
|
||||
* Returns an array mapping location result keys w/ bool values indicating whether
|
||||
* that information is supported by this provider. If it is not supported, that means
|
||||
* this provider either cannot get this information, or is not configured to get it.
|
||||
*
|
||||
* @return array eg. array(self::CONTINENT_CODE_KEY => true,
|
||||
* self::CONTINENT_NAME_KEY => true,
|
||||
* self::ORG_KEY => false)
|
||||
* The result is not guaranteed to have keys for every type of location
|
||||
* info.
|
||||
*/
|
||||
abstract public function getSupportedLocationInfo();
|
||||
|
||||
/**
|
||||
* Returns every available provider instance.
|
||||
*
|
||||
* @return LocationProvider[]
|
||||
*/
|
||||
public static function getAllProviders()
|
||||
{
|
||||
if (is_null(self::$providers)) {
|
||||
self::$providers = array();
|
||||
foreach (get_declared_classes() as $klass) {
|
||||
if (is_subclass_of($klass, 'Piwik\Plugins\UserCountry\LocationProvider')) {
|
||||
$klassInfo = new ReflectionClass($klass);
|
||||
if ($klassInfo->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$providers[] = new $klass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all provider instances that are 'available'. An 'available' provider
|
||||
* is one that is available for use. They may not necessarily be working.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getAvailableProviders()
|
||||
{
|
||||
$result = array();
|
||||
foreach (self::getAllProviders() as $provider) {
|
||||
if ($provider->isAvailable()) {
|
||||
$result[] = $provider;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array mapping provider IDs w/ information about the provider,
|
||||
* for each location provider.
|
||||
*
|
||||
* The following information is provided for each provider:
|
||||
* 'id' - The provider's unique string ID.
|
||||
* 'title' - The provider's title.
|
||||
* 'description' - A description of how the location provider works.
|
||||
* 'status' - Either self::NOT_INSTALLED, self::INSTALLED or self::BROKEN.
|
||||
* 'statusMessage' - If the status is self::BROKEN, then the message describes why.
|
||||
* 'location' - A pretty formatted location of the current IP address
|
||||
* (IP::getIpFromHeader()).
|
||||
*
|
||||
* An example result:
|
||||
* array(
|
||||
* 'geoip_php' => array('id' => 'geoip_php',
|
||||
* 'title' => '...',
|
||||
* 'desc' => '...',
|
||||
* 'status' => GeoIp::BROKEN,
|
||||
* 'statusMessage' => '...',
|
||||
* 'location' => '...')
|
||||
* 'geoip_serverbased' => array(...)
|
||||
* )
|
||||
*
|
||||
* @param string $newline What to separate lines with in the pretty locations.
|
||||
* @param bool $includeExtra Whether to include ISP/Org info in formatted location.
|
||||
* @return array
|
||||
*/
|
||||
public static function getAllProviderInfo($newline = "\n", $includeExtra = false)
|
||||
{
|
||||
$allInfo = array();
|
||||
foreach (self::getAllProviders() as $provider) {
|
||||
$info = $provider->getInfo();
|
||||
|
||||
$status = self::INSTALLED;
|
||||
$location = false;
|
||||
$statusMessage = false;
|
||||
|
||||
$availableOrMessage = $provider->isAvailable();
|
||||
if ($availableOrMessage !== true) {
|
||||
$status = self::NOT_INSTALLED;
|
||||
if (is_string($availableOrMessage)) {
|
||||
$statusMessage = $availableOrMessage;
|
||||
}
|
||||
} else {
|
||||
$workingOrError = $provider->isWorking();
|
||||
if ($workingOrError === true) // if the implementation is configured correctly, get the location
|
||||
{
|
||||
$locInfo = array('ip' => IP::getIpFromHeader(),
|
||||
'lang' => Common::getBrowserLanguage(),
|
||||
'disable_fallbacks' => true);
|
||||
|
||||
$location = $provider->getLocation($locInfo);
|
||||
$location = self::prettyFormatLocation($location, $newline, $includeExtra);
|
||||
} else // otherwise set an error message describing why
|
||||
{
|
||||
$status = self::BROKEN;
|
||||
$statusMessage = $workingOrError;
|
||||
}
|
||||
}
|
||||
|
||||
$info['status'] = $status;
|
||||
$info['statusMessage'] = $statusMessage;
|
||||
$info['location'] = $location;
|
||||
|
||||
$allInfo[$info['order']] = $info;
|
||||
}
|
||||
|
||||
ksort($allInfo);
|
||||
|
||||
$result = array();
|
||||
foreach ($allInfo as $info) {
|
||||
$result[$info['id']] = $info;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the currently used location provider.
|
||||
*
|
||||
* The used provider is stored in the 'usercountry.location_provider' option.
|
||||
*
|
||||
* This function should not be called by the Tracker.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCurrentProviderId()
|
||||
{
|
||||
$optionValue = Option::get(self::CURRENT_PROVIDER_OPTION_NAME);
|
||||
return $optionValue === false ? DefaultProvider::ID : $optionValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provider instance of the current location provider.
|
||||
*
|
||||
* This function should not be called by the Tracker.
|
||||
*
|
||||
* @return \Piwik\Plugins\UserCountry\LocationProvider
|
||||
*/
|
||||
public static function getCurrentProvider()
|
||||
{
|
||||
return self::getProviderById(self::getCurrentProviderId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provider to use when tracking.
|
||||
*
|
||||
* @param string $providerId The ID of the provider to use.
|
||||
* @return \Piwik\Plugins\UserCountry\LocationProvider The new current provider.
|
||||
* @throws Exception If the provider ID is invalid.
|
||||
*/
|
||||
public static function setCurrentProvider($providerId)
|
||||
{
|
||||
$provider = self::getProviderById($providerId);
|
||||
if ($provider === false) {
|
||||
throw new Exception(
|
||||
"Invalid provider ID '$providerId'. The provider either does not exist or is not available");
|
||||
}
|
||||
Option::set(self::CURRENT_PROVIDER_OPTION_NAME, $providerId);
|
||||
Cache::clearCacheGeneral();
|
||||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a provider instance by ID or false if the ID is invalid or unavailable.
|
||||
*
|
||||
* @param string $providerId
|
||||
* @return \Piwik\Plugins\UserCountry\LocationProvider|false
|
||||
*/
|
||||
public static function getProviderById($providerId)
|
||||
{
|
||||
foreach (self::getAvailableProviders() as $provider) {
|
||||
$info = $provider->getInfo();
|
||||
if ($info['id'] == $providerId) {
|
||||
return $provider;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to fill in any missing information in a location result.
|
||||
*
|
||||
* This method will try to set the continent code, continent name and country code
|
||||
* using other information.
|
||||
*
|
||||
* Note: This function must always be called by location providers in getLocation.
|
||||
*
|
||||
* @param array $location The location information to modify.
|
||||
*/
|
||||
public function completeLocationResult(&$location)
|
||||
{
|
||||
// fill in continent code if country code is present
|
||||
if (empty($location[self::CONTINENT_CODE_KEY])
|
||||
&& !empty($location[self::COUNTRY_CODE_KEY])
|
||||
) {
|
||||
$countryCode = strtolower($location[self::COUNTRY_CODE_KEY]);
|
||||
$location[self::CONTINENT_CODE_KEY] = Common::getContinent($countryCode);
|
||||
}
|
||||
|
||||
// fill in continent name if continent code is present
|
||||
if (empty($location[self::CONTINENT_NAME_KEY])
|
||||
&& !empty($location[self::CONTINENT_CODE_KEY])
|
||||
) {
|
||||
$continentCode = strtolower($location[self::CONTINENT_CODE_KEY]);
|
||||
$location[self::CONTINENT_NAME_KEY] = Piwik::translate('UserCountry_continent_' . $continentCode);
|
||||
}
|
||||
|
||||
// fill in country name if country code is present
|
||||
if (empty($location[self::COUNTRY_NAME_KEY])
|
||||
&& !empty($location[self::COUNTRY_CODE_KEY])
|
||||
) {
|
||||
$countryCode = strtolower($location[self::COUNTRY_CODE_KEY]);
|
||||
$location[self::COUNTRY_NAME_KEY] = Piwik::translate('UserCountry_country_' . $countryCode);
|
||||
}
|
||||
|
||||
// deal w/ improper latitude/longitude & round proper values
|
||||
if (!empty($location[self::LATITUDE_KEY])) {
|
||||
if (is_numeric($location[self::LATITUDE_KEY])) {
|
||||
$location[self::LATITUDE_KEY] = round($location[self::LATITUDE_KEY], self::GEOGRAPHIC_COORD_PRECISION);
|
||||
} else {
|
||||
unset($location[self::LATITUDE_KEY]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($location[self::LONGITUDE_KEY])) {
|
||||
if (is_numeric($location[self::LONGITUDE_KEY])) {
|
||||
$location[self::LONGITUDE_KEY] = round($location[self::LONGITUDE_KEY], self::GEOGRAPHIC_COORD_PRECISION);
|
||||
} else {
|
||||
unset($location[self::LONGITUDE_KEY]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a prettified location result.
|
||||
*
|
||||
* @param array|false $locationInfo
|
||||
* @param string $newline The line separator (ie, \n or <br/>).
|
||||
* @param bool $includeExtra Whether to include ISP/Organization info.
|
||||
* @return string
|
||||
*/
|
||||
public static function prettyFormatLocation($locationInfo, $newline = "\n", $includeExtra = false)
|
||||
{
|
||||
if ($locationInfo === false) {
|
||||
return Piwik::translate('General_Unknown');
|
||||
}
|
||||
|
||||
// add latitude/longitude line
|
||||
$lines = array();
|
||||
if (!empty($locationInfo[self::LATITUDE_KEY])
|
||||
&& !empty($locationInfo[self::LONGITUDE_KEY])
|
||||
) {
|
||||
$lines[] = '(' . $locationInfo[self::LATITUDE_KEY] . ', ' . $locationInfo[self::LONGITUDE_KEY] . ')';
|
||||
}
|
||||
|
||||
// add city/state line
|
||||
$cityState = array();
|
||||
if (!empty($locationInfo[self::CITY_NAME_KEY])) {
|
||||
$cityState[] = $locationInfo[self::CITY_NAME_KEY];
|
||||
}
|
||||
|
||||
if (!empty($locationInfo[self::REGION_CODE_KEY])) {
|
||||
$cityState[] = $locationInfo[self::REGION_CODE_KEY];
|
||||
} else if (!empty($locationInfo[self::REGION_NAME_KEY])) {
|
||||
$cityState[] = $locationInfo[self::REGION_NAME_KEY];
|
||||
}
|
||||
|
||||
if (!empty($cityState)) {
|
||||
$lines[] = implode(', ', $cityState);
|
||||
}
|
||||
|
||||
// add postal code line
|
||||
if (!empty($locationInfo[self::POSTAL_CODE_KEY])) {
|
||||
$lines[] = $locationInfo[self::POSTAL_CODE_KEY];
|
||||
}
|
||||
|
||||
// add country line
|
||||
if (!empty($locationInfo[self::COUNTRY_NAME_KEY])) {
|
||||
$lines[] = $locationInfo[self::COUNTRY_NAME_KEY];
|
||||
} else if (!empty($locationInfo[self::COUNTRY_CODE_KEY])) {
|
||||
$lines[] = $locationInfo[self::COUNTRY_CODE_KEY];
|
||||
}
|
||||
|
||||
// add extra information (ISP/Organization)
|
||||
if ($includeExtra) {
|
||||
$lines[] = '';
|
||||
|
||||
$unknown = Piwik::translate('General_Unknown');
|
||||
|
||||
$org = !empty($locationInfo[self::ORG_KEY]) ? $locationInfo[self::ORG_KEY] : $unknown;
|
||||
$lines[] = "Org: $org";
|
||||
|
||||
$isp = !empty($locationInfo[self::ISP_KEY]) ? $locationInfo[self::ISP_KEY] : $unknown;
|
||||
$lines[] = "ISP: $isp";
|
||||
}
|
||||
|
||||
return implode($newline, $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an IP address from an array that was passed into getLocation. This
|
||||
* will return an IPv4 address or false if the address is IPv6 (IPv6 is not
|
||||
* supported yet).
|
||||
*
|
||||
* @param array $info Must have 'ip' key.
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function getIpFromInfo($info)
|
||||
{
|
||||
$ip = $info['ip'];
|
||||
if (IP::isMappedIPv4($ip)) {
|
||||
return IP::getIPv4FromMappedIPv6($ip);
|
||||
} else if (IP::isIPv6($ip)) // IPv6 is not supported (yet)
|
||||
{
|
||||
return false;
|
||||
} else {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
113
www/analytics/plugins/UserCountry/LocationProvider/Default.php
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @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>', '<em>', '</em>', '</strong>'))
|
||||
. '<p><em><a href="http://piwik.org/faq/how-to/#faq_163" target="_blank">'
|
||||
. Piwik::translate('UserCountry_HowToInstallGeoIPDatabases')
|
||||
. '</em></a></p>';
|
||||
return array('id' => self::ID, 'title' => self::TITLE, 'description' => $desc, 'order' => 1);
|
||||
}
|
||||
}
|
||||
|
||||
274
www/analytics/plugins/UserCountry/LocationProvider/GeoIp.php
Executable file
|
|
@ -0,0 +1,274 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @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\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)
|
||||
{
|
||||
$this->fixupLocation($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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix up data to work with our SVG maps which include 'Tib' boundaries
|
||||
*/
|
||||
protected function fixupLocation(&$location)
|
||||
{
|
||||
if (!empty($location[self::REGION_CODE_KEY])
|
||||
&& $location[self::REGION_CODE_KEY] == '14'
|
||||
&& !empty($location[self::COUNTRY_CODE_KEY])
|
||||
&& strtoupper($location[self::COUNTRY_CODE_KEY]) == 'CN'
|
||||
) {
|
||||
$location[self::COUNTRY_CODE_KEY] = 'ti';
|
||||
$location[self::REGION_CODE_KEY] = '1';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php';
|
||||
|
||||
/**
|
||||
* @see plugins/UserCountry/LocationProvider/GeoIp/Php.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/LocationProvider/GeoIp/Php.php';
|
||||
|
||||
/**
|
||||
* @see plugins/UserCountry/LocationProvider/GeoIp/Pecl.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/LocationProvider/GeoIp/Pecl.php';
|
||||
|
||||
330
www/analytics/plugins/UserCountry/LocationProvider/GeoIp/Pecl.php
Executable file
|
|
@ -0,0 +1,330 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @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 (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 ($ip !== 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 = '<em>'
|
||||
. '<a target="_blank" href="http://piwik.org/faq/how-to/#faq_164">'
|
||||
. Piwik::translate('UserCountry_HowToInstallGeoIpPecl')
|
||||
. '</a>'
|
||||
. '</em>';
|
||||
|
||||
$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') . ': <strong><em>'
|
||||
. implode(', ', $availableDatabaseTypes) . '</em></strong>.';
|
||||
|
||||
$extraMessage = '<strong><em>' . Piwik::translate('General_Note') . ': </em></strong>' . $extraMessage;
|
||||
}
|
||||
|
||||
return array('id' => self::ID,
|
||||
'title' => self::TITLE,
|
||||
'description' => $desc,
|
||||
'install_docs' => $installDocs,
|
||||
'extra_message' => $extraMessage,
|
||||
'order' => 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
357
www/analytics/plugins/UserCountry/LocationProvider/GeoIp/Php.php
Executable file
|
|
@ -0,0 +1,357 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @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);
|
||||
|
||||
$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:
|
||||
$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:
|
||||
$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
|
||||
$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);
|
||||
|
||||
$result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr($locationGeoIp, $ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: ISP & ORG require commercial dbs to test. this code has been tested manually,
|
||||
// but not by integration tests.
|
||||
$ispGeoIp = $this->getGeoIpInstance($key = 'isp');
|
||||
if ($ispGeoIp) {
|
||||
$isp = geoip_org_by_addr($ispGeoIp, $ip);
|
||||
if (!empty($isp)) {
|
||||
$result[self::ISP_KEY] = utf8_encode($isp);
|
||||
}
|
||||
}
|
||||
|
||||
$orgGeoIp = $this->getGeoIpInstance($key = 'org');
|
||||
if ($orgGeoIp) {
|
||||
$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 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];
|
||||
}
|
||||
}
|
||||
|
||||
282
www/analytics/plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php
Executable file
|
|
@ -0,0 +1,282 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @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;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
|
||||
|
||||
/**
|
||||
* 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 (%s)';
|
||||
const TEST_SERVER_VAR = 'GEOIP_ADDR';
|
||||
const TEST_SERVER_VAR_ALT = 'GEOIP_COUNTRY_CODE';
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
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]);
|
||||
|
||||
if ($available) {
|
||||
return true;
|
||||
} else // if not available return message w/ extra info
|
||||
{
|
||||
if (!function_exists('apache_get_modules')) {
|
||||
return Piwik::translate('General_Note') . ': ' . Piwik::translate('UserCountry_AssumingNonApache');
|
||||
}
|
||||
|
||||
$message = "<strong><em>" . Piwik::translate('General_Note') . ': '
|
||||
. Piwik::translate('UserCountry_FoundApacheModules')
|
||||
. "</em></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])
|
||||
) {
|
||||
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/>'
|
||||
. '<em>' . Piwik::translate('UserCountry_GeoIpLocationProviderDesc_ServerBasedAnonWarn') . '</em>'
|
||||
. '<br/><br/>'
|
||||
. Piwik::translate('UserCountry_GeoIpLocationProviderDesc_ServerBased2',
|
||||
array('<strong><em>', '</em></strong>', '<strong><em>', '</em></strong>'));
|
||||
$installDocs =
|
||||
'<em><a target="_blank" href="http://piwik.org/faq/how-to/#faq_165">'
|
||||
. Piwik::translate('UserCountry_HowToInstallApacheModule')
|
||||
. '</a></em><br/><em>'
|
||||
. '<a target="_blank" href="http://piwik.org/faq/how-to/#faq_166">'
|
||||
. Piwik::translate('UserCountry_HowToInstallNginxModule')
|
||||
. '</a></em>';
|
||||
|
||||
$geoipServerVars = array();
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (strpos($key, 'GEOIP') === 0) {
|
||||
$geoipServerVars[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($geoipServerVars)) {
|
||||
$extraMessage = '<strong><em>' . Piwik::translate('UserCountry_GeoIPNoServerVars', '$_SERVER') . '</em></strong>';
|
||||
} else {
|
||||
$extraMessage = '<strong><em>' . Piwik::translate('UserCountry_GeoIPServerVarsFound', '$_SERVER')
|
||||
. ":</em></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' => 4,
|
||||
'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;
|
||||
}
|
||||
}
|
||||
500
www/analytics/plugins/UserCountry/UserCountry.php
Normal file
|
|
@ -0,0 +1,500 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
namespace Piwik\Plugins\UserCountry;
|
||||
|
||||
use Piwik\ArchiveProcessor;
|
||||
use Piwik\Common;
|
||||
use Piwik\Config;
|
||||
use Piwik\IP;
|
||||
use Piwik\Menu\MenuAdmin;
|
||||
use Piwik\Menu\MenuMain;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugin\Manager;
|
||||
use Piwik\Plugin\ViewDataTable;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
|
||||
use Piwik\Plugins\PrivacyManager\Config as PrivacyManagerConfig;
|
||||
use Piwik\Url;
|
||||
use Piwik\WidgetsList;
|
||||
|
||||
/**
|
||||
* @see plugins/UserCountry/GeoIPAutoUpdater.php
|
||||
*/
|
||||
require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/GeoIPAutoUpdater.php';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class UserCountry extends \Piwik\Plugin
|
||||
{
|
||||
/**
|
||||
* @see Piwik\Plugin::getListHooksRegistered
|
||||
*/
|
||||
public function getListHooksRegistered()
|
||||
{
|
||||
$hooks = array(
|
||||
'WidgetsList.addWidgets' => 'addWidgets',
|
||||
'Menu.Reporting.addItems' => 'addMenu',
|
||||
'Menu.Admin.addItems' => 'addAdminMenu',
|
||||
'Goals.getReportsWithGoalMetrics' => 'getReportsWithGoalMetrics',
|
||||
'API.getReportMetadata' => 'getReportMetadata',
|
||||
'API.getSegmentDimensionMetadata' => 'getSegmentsMetadata',
|
||||
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
|
||||
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
|
||||
'Tracker.newVisitorInformation' => 'enrichVisitWithLocation',
|
||||
'TaskScheduler.getScheduledTasks' => 'getScheduledTasks',
|
||||
'ViewDataTable.configure' => 'configureViewDataTable',
|
||||
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
|
||||
'Tracker.setTrackerCacheGeneral' => 'setTrackerCacheGeneral',
|
||||
'Insights.addReportToOverview' => 'addReportToInsightsOverview'
|
||||
);
|
||||
return $hooks;
|
||||
}
|
||||
|
||||
public function addReportToInsightsOverview(&$reports)
|
||||
{
|
||||
$reports['UserCountry_getCountry'] = array();
|
||||
}
|
||||
|
||||
public function setTrackerCacheGeneral(&$cache)
|
||||
{
|
||||
$cache['currentLocationProviderId'] = LocationProvider::getCurrentProviderId();
|
||||
}
|
||||
|
||||
public function getScheduledTasks(&$tasks)
|
||||
{
|
||||
// add the auto updater task if GeoIP admin is enabled
|
||||
if($this->isGeoLocationAdminEnabled()) {
|
||||
$tasks[] = new GeoIPAutoUpdater();
|
||||
}
|
||||
}
|
||||
|
||||
public function getStylesheetFiles(&$stylesheets)
|
||||
{
|
||||
$stylesheets[] = "plugins/UserCountry/stylesheets/userCountry.less";
|
||||
}
|
||||
|
||||
public function getJsFiles(&$jsFiles)
|
||||
{
|
||||
$jsFiles[] = "plugins/UserCountry/javascripts/userCountry.js";
|
||||
}
|
||||
|
||||
public function enrichVisitWithLocation(&$visitorInfo, \Piwik\Tracker\Request $request)
|
||||
{
|
||||
require_once PIWIK_INCLUDE_PATH . "/plugins/UserCountry/LocationProvider.php";
|
||||
|
||||
$privacyConfig = new PrivacyManagerConfig();
|
||||
|
||||
$ipAddress = IP::N2P($privacyConfig->useAnonymizedIpForVisitEnrichment ? $visitorInfo['location_ip'] : $request->getIp());
|
||||
$userInfo = array(
|
||||
'lang' => $visitorInfo['location_browser_lang'],
|
||||
'ip' => $ipAddress
|
||||
);
|
||||
|
||||
$id = Common::getCurrentLocationProviderId();
|
||||
$provider = LocationProvider::getProviderById($id);
|
||||
if ($provider === false) {
|
||||
$id = DefaultProvider::ID;
|
||||
$provider = LocationProvider::getProviderById($id);
|
||||
Common::printDebug("GEO: no current location provider sent, falling back to default '$id' one.");
|
||||
}
|
||||
|
||||
$location = $provider->getLocation($userInfo);
|
||||
|
||||
// if we can't find a location, use default provider
|
||||
if ($location === false) {
|
||||
$defaultId = DefaultProvider::ID;
|
||||
$provider = LocationProvider::getProviderById($defaultId);
|
||||
$location = $provider->getLocation($userInfo);
|
||||
Common::printDebug("GEO: couldn't find a location with Geo Module '$id', using Default '$defaultId' provider as fallback...");
|
||||
$id = $defaultId;
|
||||
}
|
||||
Common::printDebug("GEO: Found IP $ipAddress location (provider '" . $id . "'): " . var_export($location, true));
|
||||
|
||||
if (empty($location['country_code'])) { // sanity check
|
||||
$location['country_code'] = \Piwik\Tracker\Visit::UNKNOWN_CODE;
|
||||
}
|
||||
|
||||
// add optional location components
|
||||
$this->updateVisitInfoWithLocation($visitorInfo, $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets visitor info array with location info.
|
||||
*
|
||||
* @param array $visitorInfo
|
||||
* @param array $location See LocationProvider::getLocation for more info.
|
||||
*/
|
||||
private function updateVisitInfoWithLocation(&$visitorInfo, $location)
|
||||
{
|
||||
static $logVisitToLowerLocationMapping = array(
|
||||
'location_country' => LocationProvider::COUNTRY_CODE_KEY,
|
||||
);
|
||||
|
||||
static $logVisitToLocationMapping = array(
|
||||
'location_region' => LocationProvider::REGION_CODE_KEY,
|
||||
'location_city' => LocationProvider::CITY_NAME_KEY,
|
||||
'location_latitude' => LocationProvider::LATITUDE_KEY,
|
||||
'location_longitude' => LocationProvider::LONGITUDE_KEY,
|
||||
);
|
||||
|
||||
foreach ($logVisitToLowerLocationMapping as $column => $locationKey) {
|
||||
if (!empty($location[$locationKey])) {
|
||||
$visitorInfo[$column] = strtolower($location[$locationKey]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($logVisitToLocationMapping as $column => $locationKey) {
|
||||
if (!empty($location[$locationKey])) {
|
||||
$visitorInfo[$column] = $location[$locationKey];
|
||||
}
|
||||
}
|
||||
|
||||
// if the location has provider/organization info, set it
|
||||
if (!empty($location[LocationProvider::ISP_KEY])) {
|
||||
$providerValue = $location[LocationProvider::ISP_KEY];
|
||||
|
||||
// if the org is set and not the same as the isp, add it to the provider value
|
||||
if (!empty($location[LocationProvider::ORG_KEY])
|
||||
&& $location[LocationProvider::ORG_KEY] != $providerValue
|
||||
) {
|
||||
$providerValue .= ' - ' . $location[LocationProvider::ORG_KEY];
|
||||
}
|
||||
} else if (!empty($location[LocationProvider::ORG_KEY])) {
|
||||
$providerValue = $location[LocationProvider::ORG_KEY];
|
||||
}
|
||||
|
||||
if (isset($providerValue)
|
||||
&& Manager::getInstance()->isPluginInstalled('Provider')) {
|
||||
$visitorInfo['location_provider'] = $providerValue;
|
||||
}
|
||||
}
|
||||
|
||||
public function addWidgets()
|
||||
{
|
||||
$widgetContinentLabel = Piwik::translate('UserCountry_WidgetLocation')
|
||||
. ' (' . Piwik::translate('UserCountry_Continent') . ')';
|
||||
$widgetCountryLabel = Piwik::translate('UserCountry_WidgetLocation')
|
||||
. ' (' . Piwik::translate('UserCountry_Country') . ')';
|
||||
$widgetRegionLabel = Piwik::translate('UserCountry_WidgetLocation')
|
||||
. ' (' . Piwik::translate('UserCountry_Region') . ')';
|
||||
$widgetCityLabel = Piwik::translate('UserCountry_WidgetLocation')
|
||||
. ' (' . Piwik::translate('UserCountry_City') . ')';
|
||||
|
||||
WidgetsList::add('General_Visitors', $widgetContinentLabel, 'UserCountry', 'getContinent');
|
||||
WidgetsList::add('General_Visitors', $widgetCountryLabel, 'UserCountry', 'getCountry');
|
||||
WidgetsList::add('General_Visitors', $widgetRegionLabel, 'UserCountry', 'getRegion');
|
||||
WidgetsList::add('General_Visitors', $widgetCityLabel, 'UserCountry', 'getCity');
|
||||
}
|
||||
|
||||
public function addMenu()
|
||||
{
|
||||
MenuMain::getInstance()->add('General_Visitors', 'UserCountry_SubmenuLocations', array('module' => 'UserCountry', 'action' => 'index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler. Adds menu items to the MenuAdmin menu.
|
||||
*/
|
||||
public function addAdminMenu()
|
||||
{
|
||||
if($this->isGeoLocationAdminEnabled()) {
|
||||
MenuAdmin::getInstance()->add('General_Settings', 'UserCountry_Geolocation',
|
||||
array('module' => 'UserCountry', 'action' => 'adminIndex'),
|
||||
Piwik::hasUserSuperUserAccess(),
|
||||
$order = 8);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSegmentsMetadata(&$segments)
|
||||
{
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'Visit Location',
|
||||
'name' => Piwik::translate('UserCountry_Country'),
|
||||
'segment' => 'countryCode',
|
||||
'sqlSegment' => 'log_visit.location_country',
|
||||
'acceptedValues' => 'de, us, fr, in, es, etc.',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'Visit Location',
|
||||
'name' => Piwik::translate('UserCountry_Continent'),
|
||||
'segment' => 'continentCode',
|
||||
'sqlSegment' => 'log_visit.location_country',
|
||||
'acceptedValues' => 'eur, asi, amc, amn, ams, afr, ant, oce',
|
||||
'sqlFilter' => __NAMESPACE__ . '\UserCountry::getCountriesForContinent',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'Visit Location',
|
||||
'name' => Piwik::translate('UserCountry_Region'),
|
||||
'segment' => 'regionCode',
|
||||
'sqlSegment' => 'log_visit.location_region',
|
||||
'acceptedValues' => '01 02, OR, P8, etc.<br/>eg. region=A1;country=fr',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'Visit Location',
|
||||
'name' => Piwik::translate('UserCountry_City'),
|
||||
'segment' => 'city',
|
||||
'sqlSegment' => 'log_visit.location_city',
|
||||
'acceptedValues' => 'Sydney, Sao Paolo, Rome, etc.',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'Visit Location',
|
||||
'name' => Piwik::translate('UserCountry_Latitude'),
|
||||
'segment' => 'latitude',
|
||||
'sqlSegment' => 'log_visit.location_latitude',
|
||||
'acceptedValues' => '-33.578, 40.830, etc.<br/>You can select visitors within a lat/long range using &segment=lat>X;lat<Y;long>M;long<N.',
|
||||
);
|
||||
$segments[] = array(
|
||||
'type' => 'dimension',
|
||||
'category' => 'Visit Location',
|
||||
'name' => Piwik::translate('UserCountry_Longitude'),
|
||||
'segment' => 'longitude',
|
||||
'sqlSegment' => 'log_visit.location_longitude',
|
||||
'acceptedValues' => '-70.664, 14.326, etc.',
|
||||
);
|
||||
}
|
||||
|
||||
public function getReportMetadata(&$reports)
|
||||
{
|
||||
$metrics = array(
|
||||
'nb_visits' => Piwik::translate('General_ColumnNbVisits'),
|
||||
'nb_uniq_visitors' => Piwik::translate('General_ColumnNbUniqVisitors'),
|
||||
'nb_actions' => Piwik::translate('General_ColumnNbActions'),
|
||||
);
|
||||
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Visitors'),
|
||||
'name' => Piwik::translate('UserCountry_Country'),
|
||||
'module' => 'UserCountry',
|
||||
'action' => 'getCountry',
|
||||
'dimension' => Piwik::translate('UserCountry_Country'),
|
||||
'metrics' => $metrics,
|
||||
'order' => 5,
|
||||
);
|
||||
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Visitors'),
|
||||
'name' => Piwik::translate('UserCountry_Continent'),
|
||||
'module' => 'UserCountry',
|
||||
'action' => 'getContinent',
|
||||
'dimension' => Piwik::translate('UserCountry_Continent'),
|
||||
'metrics' => $metrics,
|
||||
'order' => 6,
|
||||
);
|
||||
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Visitors'),
|
||||
'name' => Piwik::translate('UserCountry_Region'),
|
||||
'module' => 'UserCountry',
|
||||
'action' => 'getRegion',
|
||||
'dimension' => Piwik::translate('UserCountry_Region'),
|
||||
'metrics' => $metrics,
|
||||
'order' => 7,
|
||||
);
|
||||
|
||||
$reports[] = array(
|
||||
'category' => Piwik::translate('General_Visitors'),
|
||||
'name' => Piwik::translate('UserCountry_City'),
|
||||
'module' => 'UserCountry',
|
||||
'action' => 'getCity',
|
||||
'dimension' => Piwik::translate('UserCountry_City'),
|
||||
'metrics' => $metrics,
|
||||
'order' => 8,
|
||||
);
|
||||
}
|
||||
|
||||
public function getReportsWithGoalMetrics(&$dimensions)
|
||||
{
|
||||
$dimensions = array_merge($dimensions, array(
|
||||
array('category' => Piwik::translate('General_Visit'),
|
||||
'name' => Piwik::translate('UserCountry_Country'),
|
||||
'module' => 'UserCountry',
|
||||
'action' => 'getCountry',
|
||||
),
|
||||
array('category' => Piwik::translate('General_Visit'),
|
||||
'name' => Piwik::translate('UserCountry_Continent'),
|
||||
'module' => 'UserCountry',
|
||||
'action' => 'getContinent',
|
||||
),
|
||||
array('category' => Piwik::translate('General_Visit'),
|
||||
'name' => Piwik::translate('UserCountry_Region'),
|
||||
'module' => 'UserCountry',
|
||||
'action' => 'getRegion'),
|
||||
array('category' => Piwik::translate('General_Visit'),
|
||||
'name' => Piwik::translate('UserCountry_City'),
|
||||
'module' => 'UserCountry',
|
||||
'action' => 'getCity'),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of country codes for a given continent code.
|
||||
*
|
||||
* @param string $continent The continent code.
|
||||
* @return array
|
||||
*/
|
||||
public static function getCountriesForContinent($continent)
|
||||
{
|
||||
$result = array();
|
||||
$continent = strtolower($continent);
|
||||
foreach (Common::getCountriesList() as $countryCode => $continentCode) {
|
||||
if ($continent == $continentCode) {
|
||||
$result[] = $countryCode;
|
||||
}
|
||||
}
|
||||
return array('SQL' => "'" . implode("', '", $result) . "', ?",
|
||||
'bind' => '-'); // HACK: SegmentExpression requires a $bind, even if there's nothing to bind
|
||||
}
|
||||
|
||||
public function configureViewDataTable(ViewDataTable $view)
|
||||
{
|
||||
switch ($view->requestConfig->apiMethodToRequestDataTable) {
|
||||
case 'UserCountry.getCountry':
|
||||
$this->configureViewForGetCountry($view);
|
||||
break;
|
||||
case 'UserCountry.getContinent':
|
||||
$this->configureViewForGetContinent($view);
|
||||
break;
|
||||
case 'UserCountry.getRegion':
|
||||
$this->configureViewForGetRegion($view);
|
||||
break;
|
||||
case 'UserCountry.getCity':
|
||||
$this->configureViewForGetCity($view);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function configureViewForGetCountry(ViewDataTable $view)
|
||||
{
|
||||
$view->config->show_goals = true;
|
||||
$view->config->show_exclude_low_population = false;
|
||||
$view->config->addTranslation('label', Piwik::translate('UserCountry_Country'));
|
||||
$view->config->documentation = Piwik::translate('UserCountry_getCountryDocumentation');
|
||||
|
||||
$view->requestConfig->filter_limit = 5;
|
||||
|
||||
if (LocationProvider::getCurrentProviderId() == DefaultProvider::ID) {
|
||||
// if we're using the default location provider, add a note explaining how it works
|
||||
$footerMessage = Piwik::translate("General_Note") . ': '
|
||||
. Piwik::translate('UserCountry_DefaultLocationProviderExplanation',
|
||||
array('<a target="_blank" href="http://piwik.org/docs/geo-locate/">', '</a>'));
|
||||
|
||||
$view->config->show_footer_message = $footerMessage;
|
||||
}
|
||||
}
|
||||
|
||||
private function configureViewForGetContinent(ViewDataTable $view)
|
||||
{
|
||||
$view->config->show_exclude_low_population = false;
|
||||
$view->config->show_goals = true;
|
||||
$view->config->show_search = false;
|
||||
$view->config->show_offset_information = false;
|
||||
$view->config->show_pagination_control = false;
|
||||
$view->config->show_limit_control = false;
|
||||
$view->config->documentation = Piwik::translate('UserCountry_getContinentDocumentation');
|
||||
$view->config->addTranslation('label', Piwik::translate('UserCountry_Continent'));
|
||||
}
|
||||
|
||||
private function configureViewForGetRegion(ViewDataTable $view)
|
||||
{
|
||||
$view->config->show_exclude_low_population = false;
|
||||
$view->config->show_goals = true;
|
||||
$view->config->documentation = Piwik::translate('UserCountry_getRegionDocumentation') . '<br/>' . $this->getGeoIPReportDocSuffix();
|
||||
$view->config->addTranslation('label', Piwik::translate('UserCountry_Region'));
|
||||
|
||||
$view->requestConfig->filter_limit = 5;
|
||||
|
||||
$this->checkIfNoDataForGeoIpReport($view);
|
||||
}
|
||||
|
||||
private function configureViewForGetCity(ViewDataTable $view)
|
||||
{
|
||||
$view->config->show_exclude_low_population = false;
|
||||
$view->config->show_goals = true;
|
||||
$view->config->documentation = Piwik::translate('UserCountry_getCityDocumentation') . '<br/>' . $this->getGeoIPReportDocSuffix();
|
||||
$view->config->addTranslation('label', Piwik::translate('UserCountry_City'));
|
||||
|
||||
$view->requestConfig->filter_limit = 5;
|
||||
|
||||
$this->checkIfNoDataForGeoIpReport($view);
|
||||
}
|
||||
|
||||
private function getGeoIPReportDocSuffix()
|
||||
{
|
||||
return Piwik::translate('UserCountry_GeoIPDocumentationSuffix',
|
||||
array('<a target="_blank" href="http://www.maxmind.com/?rId=piwik">',
|
||||
'</a>',
|
||||
'<a target="_blank" href="http://www.maxmind.com/en/city_accuracy?rId=piwik">',
|
||||
'</a>')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a datatable for a view is empty and if so, displays a message in the footer
|
||||
* telling users to configure GeoIP.
|
||||
*/
|
||||
private function checkIfNoDataForGeoIpReport(ViewDataTable $view)
|
||||
{
|
||||
$self = $this;
|
||||
$view->config->filters[] = function ($dataTable) use ($self, $view) {
|
||||
// if there's only one row whose label is 'Unknown', display a message saying there's no data
|
||||
if ($dataTable->getRowsCount() == 1
|
||||
&& $dataTable->getFirstRow()->getColumn('label') == Piwik::translate('General_Unknown')
|
||||
) {
|
||||
$footerMessage = Piwik::translate('UserCountry_NoDataForGeoIPReport1');
|
||||
|
||||
// if GeoIP is working, don't display this part of the message
|
||||
if (!$self->isGeoIPWorking()) {
|
||||
$params = array('module' => 'UserCountry', 'action' => 'adminIndex');
|
||||
$footerMessage .= ' ' . Piwik::translate('UserCountry_NoDataForGeoIPReport2',
|
||||
array('<a target="_blank" href="' . Url::getCurrentQueryStringWithParametersModified($params) . '">',
|
||||
'</a>',
|
||||
'<a target="_blank" href="http://dev.maxmind.com/geoip/geolite?rId=piwik">',
|
||||
'</a>'));
|
||||
} else {
|
||||
$footerMessage .= ' ' . Piwik::translate('UserCountry_ToGeolocateOldVisits',
|
||||
array('<a target="_blank" href="http://piwik.org/faq/how-to/#faq_167">', '</a>'));
|
||||
}
|
||||
|
||||
$view->config->show_footer_message = $footerMessage;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a GeoIP provider is installed & working, false if otherwise.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isGeoIPWorking()
|
||||
{
|
||||
$provider = LocationProvider::getCurrentProvider();
|
||||
return $provider instanceof GeoIp
|
||||
&& $provider->isAvailable() === true
|
||||
&& $provider->isWorking() === true;
|
||||
}
|
||||
|
||||
public function getClientSideTranslationKeys(&$translationKeys)
|
||||
{
|
||||
$translationKeys[] = "UserCountry_FatalErrorDuringDownload";
|
||||
$translationKeys[] = "UserCountry_SetupAutomaticUpdatesOfGeoIP";
|
||||
$translationKeys[] = "General_Done";
|
||||
}
|
||||
|
||||
public static function isGeoLocationAdminEnabled()
|
||||
{
|
||||
return (bool) Config::getInstance()->General['enable_geolocation_admin'];
|
||||
}
|
||||
|
||||
}
|
||||
169
www/analytics/plugins/UserCountry/functions.php
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
/**
|
||||
* Piwik - Open source web analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\Plugins\UserCountry;
|
||||
|
||||
use Piwik\DataTable;
|
||||
use Piwik\Piwik;
|
||||
use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
|
||||
use Piwik\Tracker\Visit;
|
||||
|
||||
/**
|
||||
* Return the flag image path for a given country
|
||||
*
|
||||
* @param string $code ISO country code
|
||||
* @return string Flag image path
|
||||
*/
|
||||
function getFlagFromCode($code)
|
||||
{
|
||||
$pathInPiwik = 'plugins/UserCountry/images/flags/%s.png';
|
||||
$pathWithCode = sprintf($pathInPiwik, $code);
|
||||
$absolutePath = PIWIK_INCLUDE_PATH . '/' . $pathWithCode;
|
||||
if (file_exists($absolutePath)) {
|
||||
return $pathWithCode;
|
||||
}
|
||||
return sprintf($pathInPiwik, Visit::UNKNOWN_CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translated continent name for a given continent code
|
||||
*
|
||||
* @param string $label Continent code
|
||||
* @return string Continent name
|
||||
*/
|
||||
function continentTranslate($label)
|
||||
{
|
||||
if ($label == 'unk' || $label == '') {
|
||||
return Piwik::translate('General_Unknown');
|
||||
}
|
||||
return Piwik::translate('UserCountry_continent_' . $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the translated country name for a given country code
|
||||
*
|
||||
* @param string $label country code
|
||||
* @return string Country name
|
||||
*/
|
||||
function countryTranslate($label)
|
||||
{
|
||||
if ($label == Visit::UNKNOWN_CODE || $label == '') {
|
||||
return Piwik::translate('General_Unknown');
|
||||
}
|
||||
return Piwik::translate('UserCountry_country_' . $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a label by a certain separator and returns the N-th element.
|
||||
*
|
||||
* @param string $label
|
||||
* @param string $separator eg. ',' or '|'
|
||||
* @param int $index The element index to extract.
|
||||
* @param mixed $emptyValue The value to remove if the element is absent. Defaults to false,
|
||||
* so no new metadata/column is added.
|
||||
* @return string|false Returns false if $label == DataTable::LABEL_SUMMARY_ROW, otherwise
|
||||
* explode($separator, $label)[$index].
|
||||
*/
|
||||
function getElementFromStringArray($label, $separator, $index, $emptyValue = false)
|
||||
{
|
||||
if ($label == DataTable::LABEL_SUMMARY_ROW) {
|
||||
return false; // so no metadata/column is added
|
||||
}
|
||||
|
||||
$segments = explode($separator, $label);
|
||||
return empty($segments[$index]) ? $emptyValue : $segments[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the region name using the label of a Visits by Region report.
|
||||
*
|
||||
* @param string $label A label containing a region code followed by '|' and a country code, eg,
|
||||
* 'P3|GB'.
|
||||
* @return string|false The region name or false if $label == DataTable::LABEL_SUMMARY_ROW.
|
||||
*/
|
||||
function getRegionName($label)
|
||||
{
|
||||
if ($label == DataTable::LABEL_SUMMARY_ROW) {
|
||||
return false; // so no metadata/column is added
|
||||
}
|
||||
|
||||
if ($label == '') {
|
||||
return Piwik::translate('General_Unknown');
|
||||
}
|
||||
|
||||
list($regionCode, $countryCode) = explode(Archiver::LOCATION_SEPARATOR, $label);
|
||||
return GeoIp::getRegionNameFromCodes($countryCode, $regionCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of a region + the name of the region's country using the label of
|
||||
* a Visits by Region report.
|
||||
*
|
||||
* @param string $label A label containing a region code followed by '|' and a country code, eg,
|
||||
* 'P3|GB'.
|
||||
* @return string|false eg. 'Ile de France, France' or false if $label == DataTable::LABEL_SUMMARY_ROW.
|
||||
*/
|
||||
function getPrettyRegionName($label)
|
||||
{
|
||||
if ($label == DataTable::LABEL_SUMMARY_ROW) {
|
||||
return $label;
|
||||
}
|
||||
|
||||
if ($label == '') {
|
||||
return Piwik::translate('General_Unknown');
|
||||
}
|
||||
|
||||
list($regionCode, $countryCode) = explode(Archiver::LOCATION_SEPARATOR, $label);
|
||||
|
||||
$result = GeoIp::getRegionNameFromCodes($countryCode, $regionCode);
|
||||
if ($countryCode != Visit::UNKNOWN_CODE && $countryCode != '') {
|
||||
$result .= ', ' . countryTranslate($countryCode);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of a city + the name of its region + the name of its country using
|
||||
* the label of a Visits by City report.
|
||||
*
|
||||
* @param string $label A label containing a city name, region code + country code,
|
||||
* separated by two '|' chars: 'Paris|A8|FR'
|
||||
* @return string|false eg. 'Paris, Ile de France, France' or false if $label ==
|
||||
* DataTable::LABEL_SUMMARY_ROW.
|
||||
*/
|
||||
function getPrettyCityName($label)
|
||||
{
|
||||
if ($label == DataTable::LABEL_SUMMARY_ROW) {
|
||||
return $label;
|
||||
}
|
||||
|
||||
if ($label == '') {
|
||||
return Piwik::translate('General_Unknown');
|
||||
}
|
||||
|
||||
// get city name, region code & country code
|
||||
$parts = explode(Archiver::LOCATION_SEPARATOR, $label);
|
||||
$cityName = $parts[0];
|
||||
$regionCode = $parts[1];
|
||||
$countryCode = @$parts[2];
|
||||
|
||||
if ($cityName == Visit::UNKNOWN_CODE || $cityName == '') {
|
||||
$cityName = Piwik::translate('General_Unknown');
|
||||
}
|
||||
|
||||
$result = $cityName;
|
||||
if ($countryCode != Visit::UNKNOWN_CODE && $countryCode != '') {
|
||||
if ($regionCode != '' && $regionCode != Visit::UNKNOWN_CODE) {
|
||||
$regionName = GeoIp::getRegionNameFromCodes($countryCode, $regionCode);
|
||||
$result .= ', ' . $regionName;
|
||||
}
|
||||
$result .= ', ' . countryTranslate($countryCode);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
BIN
www/analytics/plugins/UserCountry/images/flags/a1.png
Normal file
|
After Width: | Height: | Size: 290 B |
BIN
www/analytics/plugins/UserCountry/images/flags/a2.png
Normal file
|
After Width: | Height: | Size: 290 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ac.png
Normal file
|
After Width: | Height: | Size: 545 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ad.png
Normal file
|
After Width: | Height: | Size: 454 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ae.png
Normal file
|
After Width: | Height: | Size: 277 B |
BIN
www/analytics/plugins/UserCountry/images/flags/af.png
Normal file
|
After Width: | Height: | Size: 420 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ag.png
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ai.png
Normal file
|
After Width: | Height: | Size: 516 B |
BIN
www/analytics/plugins/UserCountry/images/flags/al.png
Normal file
|
After Width: | Height: | Size: 434 B |
BIN
www/analytics/plugins/UserCountry/images/flags/am.png
Normal file
|
After Width: | Height: | Size: 332 B |
BIN
www/analytics/plugins/UserCountry/images/flags/an.png
Normal file
|
After Width: | Height: | Size: 365 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ao.png
Normal file
|
After Width: | Height: | Size: 395 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ap.png
Normal file
|
After Width: | Height: | Size: 290 B |
BIN
www/analytics/plugins/UserCountry/images/flags/aq.png
Normal file
|
After Width: | Height: | Size: 376 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ar.png
Normal file
|
After Width: | Height: | Size: 367 B |
BIN
www/analytics/plugins/UserCountry/images/flags/as.png
Normal file
|
After Width: | Height: | Size: 540 B |
BIN
www/analytics/plugins/UserCountry/images/flags/at.png
Normal file
|
After Width: | Height: | Size: 290 B |
BIN
www/analytics/plugins/UserCountry/images/flags/au.png
Normal file
|
After Width: | Height: | Size: 580 B |
BIN
www/analytics/plugins/UserCountry/images/flags/aw.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ax.png
Normal file
|
After Width: | Height: | Size: 480 B |
BIN
www/analytics/plugins/UserCountry/images/flags/az.png
Normal file
|
After Width: | Height: | Size: 423 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ba.png
Normal file
|
After Width: | Height: | Size: 471 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bb.png
Normal file
|
After Width: | Height: | Size: 403 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bd.png
Normal file
|
After Width: | Height: | Size: 372 B |
BIN
www/analytics/plugins/UserCountry/images/flags/be.png
Normal file
|
After Width: | Height: | Size: 294 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bf.png
Normal file
|
After Width: | Height: | Size: 341 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bg.png
Normal file
|
After Width: | Height: | Size: 320 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bh.png
Normal file
|
After Width: | Height: | Size: 345 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bi.png
Normal file
|
After Width: | Height: | Size: 566 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bj.png
Normal file
|
After Width: | Height: | Size: 311 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bl.png
Normal file
|
After Width: | Height: | Size: 369 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bm.png
Normal file
|
After Width: | Height: | Size: 499 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bn.png
Normal file
|
After Width: | Height: | Size: 502 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bo.png
Normal file
|
After Width: | Height: | Size: 341 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bq.png
Normal file
|
After Width: | Height: | Size: 310 B |
BIN
www/analytics/plugins/UserCountry/images/flags/br.png
Normal file
|
After Width: | Height: | Size: 486 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bs.png
Normal file
|
After Width: | Height: | Size: 391 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bt.png
Normal file
|
After Width: | Height: | Size: 471 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bu.png
Normal file
|
After Width: | Height: | Size: 336 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bv.png
Normal file
|
After Width: | Height: | Size: 397 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bw.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
www/analytics/plugins/UserCountry/images/flags/by.png
Normal file
|
After Width: | Height: | Size: 382 B |
BIN
www/analytics/plugins/UserCountry/images/flags/bz.png
Normal file
|
After Width: | Height: | Size: 476 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ca.png
Normal file
|
After Width: | Height: | Size: 471 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cat.png
Normal file
|
After Width: | Height: | Size: 353 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cc.png
Normal file
|
After Width: | Height: | Size: 496 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cd.png
Normal file
|
After Width: | Height: | Size: 477 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cf.png
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cg.png
Normal file
|
After Width: | Height: | Size: 380 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ch.png
Normal file
|
After Width: | Height: | Size: 239 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ci.png
Normal file
|
After Width: | Height: | Size: 306 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ck.png
Normal file
|
After Width: | Height: | Size: 495 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cl.png
Normal file
|
After Width: | Height: | Size: 324 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cm.png
Normal file
|
After Width: | Height: | Size: 347 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cn.png
Normal file
|
After Width: | Height: | Size: 349 B |
BIN
www/analytics/plugins/UserCountry/images/flags/co.png
Normal file
|
After Width: | Height: | Size: 330 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cp.png
Normal file
|
After Width: | Height: | Size: 369 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cr.png
Normal file
|
After Width: | Height: | Size: 349 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cs.png
Normal file
|
After Width: | Height: | Size: 321 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cu.png
Normal file
|
After Width: | Height: | Size: 445 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cv.png
Normal file
|
After Width: | Height: | Size: 441 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cw.png
Normal file
|
After Width: | Height: | Size: 308 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cx.png
Normal file
|
After Width: | Height: | Size: 498 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cy.png
Normal file
|
After Width: | Height: | Size: 337 B |
BIN
www/analytics/plugins/UserCountry/images/flags/cz.png
Normal file
|
After Width: | Height: | Size: 367 B |
BIN
www/analytics/plugins/UserCountry/images/flags/de.png
Normal file
|
After Width: | Height: | Size: 364 B |
BIN
www/analytics/plugins/UserCountry/images/flags/dg.png
Normal file
|
After Width: | Height: | Size: 658 B |
BIN
www/analytics/plugins/UserCountry/images/flags/dj.png
Normal file
|
After Width: | Height: | Size: 430 B |
BIN
www/analytics/plugins/UserCountry/images/flags/dk.png
Normal file
|
After Width: | Height: | Size: 352 B |
BIN
www/analytics/plugins/UserCountry/images/flags/dm.png
Normal file
|
After Width: | Height: | Size: 508 B |
BIN
www/analytics/plugins/UserCountry/images/flags/do.png
Normal file
|
After Width: | Height: | Size: 368 B |
BIN
www/analytics/plugins/UserCountry/images/flags/dz.png
Normal file
|
After Width: | Height: | Size: 454 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ea.png
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ec.png
Normal file
|
After Width: | Height: | Size: 355 B |
BIN
www/analytics/plugins/UserCountry/images/flags/ee.png
Normal file
|
After Width: | Height: | Size: 297 B |
BIN
www/analytics/plugins/UserCountry/images/flags/eg.png
Normal file
|
After Width: | Height: | Size: 348 B |
BIN
www/analytics/plugins/UserCountry/images/flags/eh.png
Normal file
|
After Width: | Height: | Size: 388 B |
BIN
www/analytics/plugins/UserCountry/images/flags/er.png
Normal file
|
After Width: | Height: | Size: 497 B |
BIN
www/analytics/plugins/UserCountry/images/flags/es.png
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
www/analytics/plugins/UserCountry/images/flags/et.png
Normal file
|
After Width: | Height: | Size: 445 B |
BIN
www/analytics/plugins/UserCountry/images/flags/eu.png
Normal file
|
After Width: | Height: | Size: 418 B |
BIN
www/analytics/plugins/UserCountry/images/flags/fi.png
Normal file
|
After Width: | Height: | Size: 368 B |
BIN
www/analytics/plugins/UserCountry/images/flags/fj.png
Normal file
|
After Width: | Height: | Size: 517 B |
BIN
www/analytics/plugins/UserCountry/images/flags/fk.png
Normal file
|
After Width: | Height: | Size: 526 B |
BIN
www/analytics/plugins/UserCountry/images/flags/fm.png
Normal file
|
After Width: | Height: | Size: 409 B |
BIN
www/analytics/plugins/UserCountry/images/flags/fo.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
www/analytics/plugins/UserCountry/images/flags/fr.png
Normal file
|
After Width: | Height: | Size: 369 B |
BIN
www/analytics/plugins/UserCountry/images/flags/fx.png
Normal file
|
After Width: | Height: | Size: 369 B |