PageRenderTime 36ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/UserCountry/UserCountry.php

https://github.com/CodeYellowBV/piwik
PHP | 450 lines | 388 code | 33 blank | 29 comment | 13 complexity | 020bb30f665efd12599776039ab4aeef MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik\Plugins\UserCountry;
  10. use Piwik\ArchiveProcessor;
  11. use Piwik\Common;
  12. use Piwik\Config;
  13. use Piwik\IP;
  14. use Piwik\Piwik;
  15. use Piwik\Plugin\Manager;
  16. use Piwik\Plugin\ViewDataTable;
  17. use Piwik\Plugins\PrivacyManager\Config as PrivacyManagerConfig;
  18. use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
  19. use Piwik\Plugins\UserCountry\LocationProvider;
  20. use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider;
  21. use Piwik\Url;
  22. /**
  23. * @see plugins/UserCountry/GeoIPAutoUpdater.php
  24. */
  25. require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/GeoIPAutoUpdater.php';
  26. /**
  27. *
  28. */
  29. class UserCountry extends \Piwik\Plugin
  30. {
  31. /**
  32. * @see Piwik\Plugin::getListHooksRegistered
  33. */
  34. public function getListHooksRegistered()
  35. {
  36. $hooks = array(
  37. 'Goals.getReportsWithGoalMetrics' => 'getReportsWithGoalMetrics',
  38. 'API.getReportMetadata' => 'getReportMetadata',
  39. 'API.getSegmentDimensionMetadata' => 'getSegmentsMetadata',
  40. 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
  41. 'AssetManager.getJavaScriptFiles' => 'getJsFiles',
  42. 'Tracker.newVisitorInformation' => 'enrichVisitWithLocation',
  43. 'ViewDataTable.configure' => 'configureViewDataTable',
  44. 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
  45. 'Tracker.setTrackerCacheGeneral' => 'setTrackerCacheGeneral',
  46. 'Insights.addReportToOverview' => 'addReportToInsightsOverview'
  47. );
  48. return $hooks;
  49. }
  50. public function addReportToInsightsOverview(&$reports)
  51. {
  52. $reports['UserCountry_getCountry'] = array();
  53. }
  54. public function setTrackerCacheGeneral(&$cache)
  55. {
  56. $cache['currentLocationProviderId'] = LocationProvider::getCurrentProviderId();
  57. }
  58. public function getStylesheetFiles(&$stylesheets)
  59. {
  60. $stylesheets[] = "plugins/UserCountry/stylesheets/userCountry.less";
  61. }
  62. public function getJsFiles(&$jsFiles)
  63. {
  64. $jsFiles[] = "plugins/UserCountry/javascripts/userCountry.js";
  65. }
  66. public function enrichVisitWithLocation(&$visitorInfo, \Piwik\Tracker\Request $request)
  67. {
  68. require_once PIWIK_INCLUDE_PATH . "/plugins/UserCountry/LocationProvider.php";
  69. $privacyConfig = new PrivacyManagerConfig();
  70. $ipAddress = IP::N2P($privacyConfig->useAnonymizedIpForVisitEnrichment ? $visitorInfo['location_ip'] : $request->getIp());
  71. $userInfo = array(
  72. 'lang' => $visitorInfo['location_browser_lang'],
  73. 'ip' => $ipAddress
  74. );
  75. $id = Common::getCurrentLocationProviderId();
  76. $provider = LocationProvider::getProviderById($id);
  77. if ($provider === false) {
  78. $id = DefaultProvider::ID;
  79. $provider = LocationProvider::getProviderById($id);
  80. Common::printDebug("GEO: no current location provider sent, falling back to default '$id' one.");
  81. }
  82. $location = $provider->getLocation($userInfo);
  83. // if we can't find a location, use default provider
  84. if ($location === false) {
  85. $defaultId = DefaultProvider::ID;
  86. $provider = LocationProvider::getProviderById($defaultId);
  87. $location = $provider->getLocation($userInfo);
  88. Common::printDebug("GEO: couldn't find a location with Geo Module '$id', using Default '$defaultId' provider as fallback...");
  89. $id = $defaultId;
  90. }
  91. Common::printDebug("GEO: Found IP $ipAddress location (provider '" . $id . "'): " . var_export($location, true));
  92. if (empty($location['country_code'])) { // sanity check
  93. $location['country_code'] = \Piwik\Tracker\Visit::UNKNOWN_CODE;
  94. }
  95. // add optional location components
  96. $this->updateVisitInfoWithLocation($visitorInfo, $location);
  97. }
  98. /**
  99. * Sets visitor info array with location info.
  100. *
  101. * @param array $visitorInfo
  102. * @param array $location See LocationProvider::getLocation for more info.
  103. */
  104. private function updateVisitInfoWithLocation(&$visitorInfo, $location)
  105. {
  106. static $logVisitToLowerLocationMapping = array(
  107. 'location_country' => LocationProvider::COUNTRY_CODE_KEY,
  108. );
  109. static $logVisitToLocationMapping = array(
  110. 'location_region' => LocationProvider::REGION_CODE_KEY,
  111. 'location_city' => LocationProvider::CITY_NAME_KEY,
  112. 'location_latitude' => LocationProvider::LATITUDE_KEY,
  113. 'location_longitude' => LocationProvider::LONGITUDE_KEY,
  114. );
  115. foreach ($logVisitToLowerLocationMapping as $column => $locationKey) {
  116. if (!empty($location[$locationKey])) {
  117. $visitorInfo[$column] = strtolower($location[$locationKey]);
  118. }
  119. }
  120. foreach ($logVisitToLocationMapping as $column => $locationKey) {
  121. if (!empty($location[$locationKey])) {
  122. $visitorInfo[$column] = $location[$locationKey];
  123. }
  124. }
  125. // if the location has provider/organization info, set it
  126. if (!empty($location[LocationProvider::ISP_KEY])) {
  127. $providerValue = $location[LocationProvider::ISP_KEY];
  128. // if the org is set and not the same as the isp, add it to the provider value
  129. if (!empty($location[LocationProvider::ORG_KEY])
  130. && $location[LocationProvider::ORG_KEY] != $providerValue
  131. ) {
  132. $providerValue .= ' - ' . $location[LocationProvider::ORG_KEY];
  133. }
  134. } else if (!empty($location[LocationProvider::ORG_KEY])) {
  135. $providerValue = $location[LocationProvider::ORG_KEY];
  136. }
  137. if (isset($providerValue)
  138. && Manager::getInstance()->isPluginInstalled('Provider')) {
  139. $visitorInfo['location_provider'] = $providerValue;
  140. }
  141. }
  142. public function getSegmentsMetadata(&$segments)
  143. {
  144. $segments[] = array(
  145. 'type' => 'dimension',
  146. 'category' => 'Visit Location',
  147. 'name' => Piwik::translate('UserCountry_Country'),
  148. 'segment' => 'countryCode',
  149. 'sqlSegment' => 'log_visit.location_country',
  150. 'acceptedValues' => 'de, us, fr, in, es, etc.',
  151. );
  152. $segments[] = array(
  153. 'type' => 'dimension',
  154. 'category' => 'Visit Location',
  155. 'name' => Piwik::translate('UserCountry_Continent'),
  156. 'segment' => 'continentCode',
  157. 'sqlSegment' => 'log_visit.location_country',
  158. 'acceptedValues' => 'eur, asi, amc, amn, ams, afr, ant, oce',
  159. 'sqlFilter' => __NAMESPACE__ . '\UserCountry::getCountriesForContinent',
  160. );
  161. $segments[] = array(
  162. 'type' => 'dimension',
  163. 'category' => 'Visit Location',
  164. 'name' => Piwik::translate('UserCountry_Region'),
  165. 'segment' => 'regionCode',
  166. 'sqlSegment' => 'log_visit.location_region',
  167. 'acceptedValues' => '01 02, OR, P8, etc.<br/>eg. region=A1;country=fr',
  168. );
  169. $segments[] = array(
  170. 'type' => 'dimension',
  171. 'category' => 'Visit Location',
  172. 'name' => Piwik::translate('UserCountry_City'),
  173. 'segment' => 'city',
  174. 'sqlSegment' => 'log_visit.location_city',
  175. 'acceptedValues' => 'Sydney, Sao Paolo, Rome, etc.',
  176. );
  177. $segments[] = array(
  178. 'type' => 'dimension',
  179. 'category' => 'Visit Location',
  180. 'name' => Piwik::translate('UserCountry_Latitude'),
  181. 'segment' => 'latitude',
  182. 'sqlSegment' => 'log_visit.location_latitude',
  183. 'acceptedValues' => '-33.578, 40.830, etc.<br/>You can select visitors within a lat/long range using &segment=lat&gt;X;lat&lt;Y;long&gt;M;long&lt;N.',
  184. );
  185. $segments[] = array(
  186. 'type' => 'dimension',
  187. 'category' => 'Visit Location',
  188. 'name' => Piwik::translate('UserCountry_Longitude'),
  189. 'segment' => 'longitude',
  190. 'sqlSegment' => 'log_visit.location_longitude',
  191. 'acceptedValues' => '-70.664, 14.326, etc.',
  192. );
  193. }
  194. public function getReportMetadata(&$reports)
  195. {
  196. $metrics = array(
  197. 'nb_visits' => Piwik::translate('General_ColumnNbVisits'),
  198. 'nb_uniq_visitors' => Piwik::translate('General_ColumnNbUniqVisitors'),
  199. 'nb_actions' => Piwik::translate('General_ColumnNbActions'),
  200. );
  201. $reports[] = array(
  202. 'category' => Piwik::translate('General_Visitors'),
  203. 'name' => Piwik::translate('UserCountry_Country'),
  204. 'module' => 'UserCountry',
  205. 'action' => 'getCountry',
  206. 'dimension' => Piwik::translate('UserCountry_Country'),
  207. 'metrics' => $metrics,
  208. 'order' => 5,
  209. );
  210. $reports[] = array(
  211. 'category' => Piwik::translate('General_Visitors'),
  212. 'name' => Piwik::translate('UserCountry_Continent'),
  213. 'module' => 'UserCountry',
  214. 'action' => 'getContinent',
  215. 'dimension' => Piwik::translate('UserCountry_Continent'),
  216. 'metrics' => $metrics,
  217. 'order' => 6,
  218. );
  219. $reports[] = array(
  220. 'category' => Piwik::translate('General_Visitors'),
  221. 'name' => Piwik::translate('UserCountry_Region'),
  222. 'module' => 'UserCountry',
  223. 'action' => 'getRegion',
  224. 'dimension' => Piwik::translate('UserCountry_Region'),
  225. 'metrics' => $metrics,
  226. 'order' => 7,
  227. );
  228. $reports[] = array(
  229. 'category' => Piwik::translate('General_Visitors'),
  230. 'name' => Piwik::translate('UserCountry_City'),
  231. 'module' => 'UserCountry',
  232. 'action' => 'getCity',
  233. 'dimension' => Piwik::translate('UserCountry_City'),
  234. 'metrics' => $metrics,
  235. 'order' => 8,
  236. );
  237. }
  238. public function getReportsWithGoalMetrics(&$dimensions)
  239. {
  240. $dimensions = array_merge($dimensions, array(
  241. array('category' => Piwik::translate('General_Visit'),
  242. 'name' => Piwik::translate('UserCountry_Country'),
  243. 'module' => 'UserCountry',
  244. 'action' => 'getCountry',
  245. ),
  246. array('category' => Piwik::translate('General_Visit'),
  247. 'name' => Piwik::translate('UserCountry_Continent'),
  248. 'module' => 'UserCountry',
  249. 'action' => 'getContinent',
  250. ),
  251. array('category' => Piwik::translate('General_Visit'),
  252. 'name' => Piwik::translate('UserCountry_Region'),
  253. 'module' => 'UserCountry',
  254. 'action' => 'getRegion'),
  255. array('category' => Piwik::translate('General_Visit'),
  256. 'name' => Piwik::translate('UserCountry_City'),
  257. 'module' => 'UserCountry',
  258. 'action' => 'getCity'),
  259. ));
  260. }
  261. /**
  262. * Returns a list of country codes for a given continent code.
  263. *
  264. * @param string $continent The continent code.
  265. * @return array
  266. */
  267. public static function getCountriesForContinent($continent)
  268. {
  269. $result = array();
  270. $continent = strtolower($continent);
  271. foreach (Common::getCountriesList() as $countryCode => $continentCode) {
  272. if ($continent == $continentCode) {
  273. $result[] = $countryCode;
  274. }
  275. }
  276. return array('SQL' => "'" . implode("', '", $result) . "', ?",
  277. 'bind' => '-'); // HACK: SegmentExpression requires a $bind, even if there's nothing to bind
  278. }
  279. public function configureViewDataTable(ViewDataTable $view)
  280. {
  281. switch ($view->requestConfig->apiMethodToRequestDataTable) {
  282. case 'UserCountry.getCountry':
  283. $this->configureViewForGetCountry($view);
  284. break;
  285. case 'UserCountry.getContinent':
  286. $this->configureViewForGetContinent($view);
  287. break;
  288. case 'UserCountry.getRegion':
  289. $this->configureViewForGetRegion($view);
  290. break;
  291. case 'UserCountry.getCity':
  292. $this->configureViewForGetCity($view);
  293. break;
  294. }
  295. }
  296. private function configureViewForGetCountry(ViewDataTable $view)
  297. {
  298. $view->config->show_goals = true;
  299. $view->config->show_exclude_low_population = false;
  300. $view->config->addTranslation('label', Piwik::translate('UserCountry_Country'));
  301. $view->config->documentation = Piwik::translate('UserCountry_getCountryDocumentation');
  302. $view->requestConfig->filter_limit = 5;
  303. if (LocationProvider::getCurrentProviderId() == DefaultProvider::ID) {
  304. // if we're using the default location provider, add a note explaining how it works
  305. $footerMessage = Piwik::translate("General_Note") . ': '
  306. . Piwik::translate('UserCountry_DefaultLocationProviderExplanation',
  307. array('<a target="_blank" href="http://piwik.org/docs/geo-locate/">', '</a>'));
  308. $view->config->show_footer_message = $footerMessage;
  309. }
  310. }
  311. private function configureViewForGetContinent(ViewDataTable $view)
  312. {
  313. $view->config->show_exclude_low_population = false;
  314. $view->config->show_goals = true;
  315. $view->config->show_search = false;
  316. $view->config->show_offset_information = false;
  317. $view->config->show_pagination_control = false;
  318. $view->config->show_limit_control = false;
  319. $view->config->documentation = Piwik::translate('UserCountry_getContinentDocumentation');
  320. $view->config->addTranslation('label', Piwik::translate('UserCountry_Continent'));
  321. }
  322. private function configureViewForGetRegion(ViewDataTable $view)
  323. {
  324. $view->config->show_exclude_low_population = false;
  325. $view->config->show_goals = true;
  326. $view->config->documentation = Piwik::translate('UserCountry_getRegionDocumentation') . '<br/>' . $this->getGeoIPReportDocSuffix();
  327. $view->config->addTranslation('label', Piwik::translate('UserCountry_Region'));
  328. $view->requestConfig->filter_limit = 5;
  329. $this->checkIfNoDataForGeoIpReport($view);
  330. }
  331. private function configureViewForGetCity(ViewDataTable $view)
  332. {
  333. $view->config->show_exclude_low_population = false;
  334. $view->config->show_goals = true;
  335. $view->config->documentation = Piwik::translate('UserCountry_getCityDocumentation') . '<br/>' . $this->getGeoIPReportDocSuffix();
  336. $view->config->addTranslation('label', Piwik::translate('UserCountry_City'));
  337. $view->requestConfig->filter_limit = 5;
  338. $this->checkIfNoDataForGeoIpReport($view);
  339. }
  340. private function getGeoIPReportDocSuffix()
  341. {
  342. return Piwik::translate('UserCountry_GeoIPDocumentationSuffix',
  343. array('<a target="_blank" href="http://www.maxmind.com/?rId=piwik">',
  344. '</a>',
  345. '<a target="_blank" href="http://www.maxmind.com/en/city_accuracy?rId=piwik">',
  346. '</a>')
  347. );
  348. }
  349. /**
  350. * Checks if a datatable for a view is empty and if so, displays a message in the footer
  351. * telling users to configure GeoIP.
  352. */
  353. private function checkIfNoDataForGeoIpReport(ViewDataTable $view)
  354. {
  355. $self = $this;
  356. $view->config->filters[] = function ($dataTable) use ($self, $view) {
  357. // if there's only one row whose label is 'Unknown', display a message saying there's no data
  358. if ($dataTable->getRowsCount() == 1
  359. && $dataTable->getFirstRow()->getColumn('label') == Piwik::translate('General_Unknown')
  360. ) {
  361. $footerMessage = Piwik::translate('UserCountry_NoDataForGeoIPReport1');
  362. // if GeoIP is working, don't display this part of the message
  363. if (!$self->isGeoIPWorking()) {
  364. $params = array('module' => 'UserCountry', 'action' => 'adminIndex');
  365. $footerMessage .= ' ' . Piwik::translate('UserCountry_NoDataForGeoIPReport2',
  366. array('<a target="_blank" href="' . Url::getCurrentQueryStringWithParametersModified($params) . '">',
  367. '</a>',
  368. '<a target="_blank" href="http://dev.maxmind.com/geoip/geolite?rId=piwik">',
  369. '</a>'));
  370. } else {
  371. $footerMessage .= ' ' . Piwik::translate('UserCountry_ToGeolocateOldVisits',
  372. array('<a target="_blank" href="http://piwik.org/faq/how-to/#faq_167">', '</a>'));
  373. }
  374. $view->config->show_footer_message = $footerMessage;
  375. }
  376. };
  377. }
  378. /**
  379. * Returns true if a GeoIP provider is installed & working, false if otherwise.
  380. *
  381. * @return bool
  382. */
  383. public function isGeoIPWorking()
  384. {
  385. $provider = LocationProvider::getCurrentProvider();
  386. return $provider instanceof GeoIp
  387. && $provider->isAvailable() === true
  388. && $provider->isWorking() === true;
  389. }
  390. public function getClientSideTranslationKeys(&$translationKeys)
  391. {
  392. $translationKeys[] = "UserCountry_FatalErrorDuringDownload";
  393. $translationKeys[] = "UserCountry_SetupAutomaticUpdatesOfGeoIP";
  394. $translationKeys[] = "General_Done";
  395. }
  396. public static function isGeoLocationAdminEnabled()
  397. {
  398. return (bool) Config::getInstance()->General['enable_geolocation_admin'];
  399. }
  400. }