PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php

https://github.com/CodeYellowBV/piwik
PHP | 282 lines | 177 code | 27 blank | 78 comment | 27 complexity | 9291cf4af090561767f68b5caf562bd4 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\LocationProvider\GeoIp;
  10. use Piwik\Common;
  11. use Piwik\IP;
  12. use Piwik\Piwik;
  13. use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
  14. use Piwik\Plugins\UserCountry\LocationProvider;
  15. /**
  16. * A LocationProvider that uses an GeoIP module installed in an HTTP Server.
  17. *
  18. * To make this provider available, make sure the GEOIP_ADDR server
  19. * variable is set.
  20. *
  21. */
  22. class ServerBased extends GeoIp
  23. {
  24. const ID = 'geoip_serverbased';
  25. const TITLE = 'GeoIP (%s)';
  26. const TEST_SERVER_VAR = 'GEOIP_ADDR';
  27. const TEST_SERVER_VAR_ALT = 'GEOIP_COUNTRY_CODE';
  28. private static $geoIpServerVars = array(
  29. parent::COUNTRY_CODE_KEY => 'GEOIP_COUNTRY_CODE',
  30. parent::COUNTRY_NAME_KEY => 'GEOIP_COUNTRY_NAME',
  31. parent::REGION_CODE_KEY => 'GEOIP_REGION',
  32. parent::REGION_NAME_KEY => 'GEOIP_REGION_NAME',
  33. parent::AREA_CODE_KEY => 'GEOIP_AREA_CODE',
  34. parent::LATITUDE_KEY => 'GEOIP_LATITUDE',
  35. parent::LONGITUDE_KEY => 'GEOIP_LONGITUDE',
  36. parent::POSTAL_CODE_KEY => 'GEOIP_POSTAL_CODE',
  37. );
  38. private static $geoIpUtfServerVars = array(
  39. parent::CITY_NAME_KEY => 'GEOIP_CITY',
  40. parent::ISP_KEY => 'GEOIP_ISP',
  41. parent::ORG_KEY => 'GEOIP_ORGANIZATION',
  42. );
  43. /**
  44. * Uses a GeoIP database to get a visitor's location based on their IP address.
  45. *
  46. * This function will return different results based on the data used and based
  47. * on how the GeoIP module is configured.
  48. *
  49. * If a region database is used, it may return the country code, region code,
  50. * city name, area code, latitude, longitude and postal code of the visitor.
  51. *
  52. * Alternatively, only the country code may be returned for another database.
  53. *
  54. * If your HTTP server is not configured to include all GeoIP information, some
  55. * information will not be available to Piwik.
  56. *
  57. * @param array $info Must have an 'ip' field.
  58. * @return array
  59. */
  60. public function getLocation($info)
  61. {
  62. $ip = $this->getIpFromInfo($info);
  63. // geoip modules that are built into servers can't use a forced IP. in this case we try
  64. // to fallback to another version.
  65. $myIP = IP::getIpFromHeader();
  66. if (!self::isSameOrAnonymizedIp($ip, $myIP)
  67. && (!isset($info['disable_fallbacks'])
  68. || !$info['disable_fallbacks'])
  69. ) {
  70. 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... ");
  71. $fallbacks = array(
  72. Pecl::ID,
  73. Php::ID
  74. );
  75. foreach ($fallbacks as $fallbackProviderId) {
  76. $otherProvider = LocationProvider::getProviderById($fallbackProviderId);
  77. if ($otherProvider) {
  78. Common::printDebug("Used $fallbackProviderId to detect this visitor IP");
  79. return $otherProvider->getLocation($info);
  80. }
  81. }
  82. 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.");
  83. return false;
  84. }
  85. $result = array();
  86. foreach (self::$geoIpServerVars as $resultKey => $geoipVarName) {
  87. if (!empty($_SERVER[$geoipVarName])) {
  88. $result[$resultKey] = $_SERVER[$geoipVarName];
  89. }
  90. }
  91. foreach (self::$geoIpUtfServerVars as $resultKey => $geoipVarName) {
  92. if (!empty($_SERVER[$geoipVarName])) {
  93. $result[$resultKey] = utf8_encode($_SERVER[$geoipVarName]);
  94. }
  95. }
  96. $this->completeLocationResult($result);
  97. return $result;
  98. }
  99. /**
  100. * Returns an array describing the types of location information this provider will
  101. * return.
  102. *
  103. * There's no way to tell exactly what database the HTTP server is using, so we just
  104. * assume country and continent information is available. This can make diagnostics
  105. * a bit more difficult, unfortunately.
  106. *
  107. * @return array
  108. */
  109. public function getSupportedLocationInfo()
  110. {
  111. $result = array();
  112. // assume country info is always available. it's an error if it's not.
  113. $result[self::COUNTRY_CODE_KEY] = true;
  114. $result[self::COUNTRY_NAME_KEY] = true;
  115. $result[self::CONTINENT_CODE_KEY] = true;
  116. $result[self::CONTINENT_NAME_KEY] = true;
  117. return $result;
  118. }
  119. /**
  120. * Checks if an HTTP server module has been installed. It checks by looking for
  121. * the GEOIP_ADDR server variable.
  122. *
  123. * There's a special check for the Apache module, but we can't check specifically
  124. * for anything else.
  125. *
  126. * @return bool|string
  127. */
  128. public function isAvailable()
  129. {
  130. // check if apache module is installed
  131. if (function_exists('apache_get_modules')) {
  132. foreach (apache_get_modules() as $name) {
  133. if (strpos($name, 'geoip') !== false) {
  134. return true;
  135. }
  136. }
  137. }
  138. $available = !empty($_SERVER[self::TEST_SERVER_VAR])
  139. || !empty($_SERVER[self::TEST_SERVER_VAR_ALT]);
  140. if ($available) {
  141. return true;
  142. } else // if not available return message w/ extra info
  143. {
  144. if (!function_exists('apache_get_modules')) {
  145. return Piwik::translate('General_Note') . ':&nbsp;' . Piwik::translate('UserCountry_AssumingNonApache');
  146. }
  147. $message = "<strong><em>" . Piwik::translate('General_Note') . ':&nbsp;'
  148. . Piwik::translate('UserCountry_FoundApacheModules')
  149. . "</em></strong>:<br/><br/>\n<ul style=\"list-style:disc;margin-left:24px\">\n";
  150. foreach (apache_get_modules() as $name) {
  151. $message .= "<li>$name</li>\n";
  152. }
  153. $message .= "</ul>";
  154. return $message;
  155. }
  156. }
  157. /**
  158. * Returns true if the GEOIP_ADDR server variable is defined.
  159. *
  160. * @return bool
  161. */
  162. public function isWorking()
  163. {
  164. if (empty($_SERVER[self::TEST_SERVER_VAR])
  165. && empty($_SERVER[self::TEST_SERVER_VAR_ALT])
  166. ) {
  167. return Piwik::translate("UserCountry_CannotFindGeoIPServerVar", self::TEST_SERVER_VAR . ' $_SERVER');
  168. }
  169. return true; // can't check for another IP
  170. }
  171. /**
  172. * Returns information about this location provider. Contains an id, title & description:
  173. *
  174. * array(
  175. * 'id' => 'geoip_serverbased',
  176. * 'title' => '...',
  177. * 'description' => '...'
  178. * );
  179. *
  180. * @return array
  181. */
  182. public function getInfo()
  183. {
  184. if (function_exists('apache_note')) {
  185. $serverDesc = 'Apache';
  186. } else {
  187. $serverDesc = Piwik::translate('UserCountry_HttpServerModule');
  188. }
  189. $title = sprintf(self::TITLE, $serverDesc);
  190. $desc = Piwik::translate('UserCountry_GeoIpLocationProviderDesc_ServerBased1', array('<strong>', '</strong>'))
  191. . '<br/><br/>'
  192. . '<em>' . Piwik::translate('UserCountry_GeoIpLocationProviderDesc_ServerBasedAnonWarn') . '</em>'
  193. . '<br/><br/>'
  194. . Piwik::translate('UserCountry_GeoIpLocationProviderDesc_ServerBased2',
  195. array('<strong><em>', '</em></strong>', '<strong><em>', '</em></strong>'));
  196. $installDocs =
  197. '<em><a target="_blank" href="http://piwik.org/faq/how-to/#faq_165">'
  198. . Piwik::translate('UserCountry_HowToInstallApacheModule')
  199. . '</a></em><br/><em>'
  200. . '<a target="_blank" href="http://piwik.org/faq/how-to/#faq_166">'
  201. . Piwik::translate('UserCountry_HowToInstallNginxModule')
  202. . '</a></em>';
  203. $geoipServerVars = array();
  204. foreach ($_SERVER as $key => $value) {
  205. if (strpos($key, 'GEOIP') === 0) {
  206. $geoipServerVars[] = $key;
  207. }
  208. }
  209. if (empty($geoipServerVars)) {
  210. $extraMessage = '<strong><em>' . Piwik::translate('UserCountry_GeoIPNoServerVars', '$_SERVER') . '</em></strong>';
  211. } else {
  212. $extraMessage = '<strong><em>' . Piwik::translate('UserCountry_GeoIPServerVarsFound', '$_SERVER')
  213. . ":</em></strong><br/><br/>\n<ul style=\"list-style:disc;margin-left:24px\">\n";
  214. foreach ($geoipServerVars as $key) {
  215. $extraMessage .= '<li>' . $key . "</li>\n";
  216. }
  217. $extraMessage .= '</ul>';
  218. }
  219. return array('id' => self::ID,
  220. 'title' => $title,
  221. 'description' => $desc,
  222. 'order' => 4,
  223. 'install_docs' => $installDocs,
  224. 'extra_message' => $extraMessage);
  225. }
  226. /**
  227. * Checks if two IP addresses are the same or if the first is the anonymized
  228. * version of the other.
  229. *
  230. * @param string $ip
  231. * @param string $currentIp This IP should not be anonymized.
  232. * @return bool
  233. */
  234. public static function isSameOrAnonymizedIp($ip, $currentIp)
  235. {
  236. $ip = array_reverse(explode('.', $ip));
  237. $currentIp = array_reverse(explode('.', $currentIp));
  238. if (count($ip) != count($currentIp)) {
  239. return false;
  240. }
  241. foreach ($ip as $i => $byte) {
  242. if ($byte == 0) {
  243. $currentIp[$i] = 0;
  244. } else {
  245. break;
  246. }
  247. }
  248. foreach ($ip as $i => $byte) {
  249. if ($byte != $currentIp[$i]) {
  250. return false;
  251. }
  252. }
  253. return true;
  254. }
  255. }