500 lines
21 KiB
PHP
500 lines
21 KiB
PHP
<?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'];
|
|
}
|
|
|
|
}
|