PageRenderTime 43ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/UserCountry/LocationProvider/GeoIp/Php.php

https://github.com/CodeYellowBV/piwik
PHP | 357 lines | 203 code | 36 blank | 118 comment | 29 complexity | 3d9c01ac2478f80197282e0fc7e5f006 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\Log;
  11. use Piwik\Piwik;
  12. use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
  13. /**
  14. * A LocationProvider that uses the PHP implementation of GeoIP.
  15. *
  16. */
  17. class Php extends GeoIp
  18. {
  19. const ID = 'geoip_php';
  20. const TITLE = 'GeoIP (Php)';
  21. /**
  22. * The GeoIP database instances used. This array will contain at most three
  23. * of them: one for location info, one for ISP info and another for organization
  24. * info.
  25. *
  26. * Each instance is mapped w/ one of the following keys: 'loc', 'isp', 'org'
  27. *
  28. * @var array of GeoIP instances
  29. */
  30. private $geoIpCache = array();
  31. /**
  32. * Possible filenames for each type of GeoIP database. When looking for a database
  33. * file in the 'misc' subdirectory, files with these names will be looked for.
  34. *
  35. * This variable is an array mapping either the 'loc', 'isp' or 'org' strings with
  36. * an array of filenames.
  37. *
  38. * By default, this will be set to Php::$dbNames.
  39. *
  40. * @var array
  41. */
  42. private $customDbNames;
  43. /**
  44. * Constructor.
  45. *
  46. * @param array|bool $customDbNames The possible filenames for each type of GeoIP database.
  47. * eg array(
  48. * 'loc' => array('GeoLiteCity.dat'),
  49. * 'isp' => array('GeoIP.dat', 'GeoIPISP.dat')
  50. * 'org' => array('GeoIPOrg.dat')
  51. * )
  52. * If a key is missing (or the parameter not supplied), then the
  53. * default database names are used.
  54. */
  55. public function __construct($customDbNames = false)
  56. {
  57. $this->customDbNames = parent::$dbNames;
  58. if ($customDbNames !== false) {
  59. foreach ($this->customDbNames as $key => $names) {
  60. if (isset($customDbNames[$key])) {
  61. $this->customDbNames[$key] = $customDbNames[$key];
  62. }
  63. }
  64. }
  65. }
  66. /**
  67. * Closes all open geoip instances.
  68. */
  69. public function __destruct()
  70. {
  71. foreach ($this->geoIpCache as $instance) {
  72. geoip_close($instance);
  73. }
  74. }
  75. /**
  76. * Uses a GeoIP database to get a visitor's location based on their IP address.
  77. *
  78. * This function will return different results based on the data used. If a city
  79. * database is used, it may return the country code, region code, city name, area
  80. * code, latitude, longitude and postal code of the visitor.
  81. *
  82. * Alternatively, if used with a country database, only the country code will be
  83. * returned.
  84. *
  85. * @param array $info Must have an 'ip' field.
  86. * @return array
  87. */
  88. public function getLocation($info)
  89. {
  90. $ip = $this->getIpFromInfo($info);
  91. $result = array();
  92. $locationGeoIp = $this->getGeoIpInstance($key = 'loc');
  93. if ($locationGeoIp) {
  94. switch ($locationGeoIp->databaseType) {
  95. case GEOIP_CITY_EDITION_REV0: // city database type
  96. case GEOIP_CITY_EDITION_REV1:
  97. case GEOIP_CITYCOMBINED_EDITION:
  98. $location = geoip_record_by_addr($locationGeoIp, $ip);
  99. if (!empty($location)) {
  100. $result[self::COUNTRY_CODE_KEY] = $location->country_code;
  101. $result[self::REGION_CODE_KEY] = $location->region;
  102. $result[self::CITY_NAME_KEY] = utf8_encode($location->city);
  103. $result[self::AREA_CODE_KEY] = $location->area_code;
  104. $result[self::LATITUDE_KEY] = $location->latitude;
  105. $result[self::LONGITUDE_KEY] = $location->longitude;
  106. $result[self::POSTAL_CODE_KEY] = $location->postal_code;
  107. }
  108. break;
  109. case GEOIP_REGION_EDITION_REV0: // region database type
  110. case GEOIP_REGION_EDITION_REV1:
  111. $location = geoip_region_by_addr($locationGeoIp, $ip);
  112. if (!empty($location)) {
  113. $result[self::COUNTRY_CODE_KEY] = $location[0];
  114. $result[self::REGION_CODE_KEY] = $location[1];
  115. }
  116. break;
  117. case GEOIP_COUNTRY_EDITION: // country database type
  118. $result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr($locationGeoIp, $ip);
  119. break;
  120. default: // unknown database type, log warning and fallback to country edition
  121. Log::warning("Found unrecognized database type: %s", $locationGeoIp->databaseType);
  122. $result[self::COUNTRY_CODE_KEY] = geoip_country_code_by_addr($locationGeoIp, $ip);
  123. break;
  124. }
  125. }
  126. // NOTE: ISP & ORG require commercial dbs to test. this code has been tested manually,
  127. // but not by integration tests.
  128. $ispGeoIp = $this->getGeoIpInstance($key = 'isp');
  129. if ($ispGeoIp) {
  130. $isp = geoip_org_by_addr($ispGeoIp, $ip);
  131. if (!empty($isp)) {
  132. $result[self::ISP_KEY] = utf8_encode($isp);
  133. }
  134. }
  135. $orgGeoIp = $this->getGeoIpInstance($key = 'org');
  136. if ($orgGeoIp) {
  137. $org = geoip_org_by_addr($orgGeoIp, $ip);
  138. if (!empty($org)) {
  139. $result[self::ORG_KEY] = utf8_encode($org);
  140. }
  141. }
  142. if (empty($result)) {
  143. return false;
  144. }
  145. $this->completeLocationResult($result);
  146. return $result;
  147. }
  148. /**
  149. * Returns true if this location provider is available. Piwik ships w/ the MaxMind
  150. * PHP library, so this provider is available if a location GeoIP database can be found.
  151. *
  152. * @return bool
  153. */
  154. public function isAvailable()
  155. {
  156. $path = self::getPathToGeoIpDatabase($this->customDbNames['loc']);
  157. return $path !== false;
  158. }
  159. /**
  160. * Returns true if this provider has been setup correctly, the error message if
  161. * otherwise.
  162. *
  163. * @return bool|string
  164. */
  165. public function isWorking()
  166. {
  167. if (!function_exists('mb_internal_encoding')) {
  168. return Piwik::translate('UserCountry_GeoIPCannotFindMbstringExtension',
  169. array('mb_internal_encoding', 'mbstring'));
  170. }
  171. $geoIpError = false;
  172. $catchGeoIpError = function ($errno, $errstr, $errfile, $errline) use (&$geoIpError) {
  173. $filename = basename($errfile);
  174. if ($filename == 'geoip.inc'
  175. || $filename == 'geoipcity.inc'
  176. ) {
  177. $geoIpError = array($errno, $errstr, $errfile, $errline);
  178. } else {
  179. throw new \Exception("Error in PHP GeoIP provider: $errstr on line $errline of $errfile"); // unexpected
  180. }
  181. };
  182. // catch GeoIP errors
  183. set_error_handler($catchGeoIpError);
  184. $result = parent::isWorking();
  185. restore_error_handler();
  186. if ($geoIpError) {
  187. list($errno, $errstr, $errfile, $errline) = $geoIpError;
  188. Log::warning("Got GeoIP error when testing PHP GeoIP location provider: %s(%s): %s", $errfile, $errline, $errstr);
  189. return Piwik::translate('UserCountry_GeoIPIncorrectDatabaseFormat');
  190. }
  191. return $result;
  192. }
  193. /**
  194. * Returns an array describing the types of location information this provider will
  195. * return.
  196. *
  197. * The location info this provider supports depends on what GeoIP databases it can
  198. * find.
  199. *
  200. * This provider will always support country & continent information.
  201. *
  202. * If a region database is found, then region code & name information will be
  203. * supported.
  204. *
  205. * If a city database is found, then region code, region name, city name,
  206. * area code, latitude, longitude & postal code are all supported.
  207. *
  208. * If an organization database is found, organization information is
  209. * supported.
  210. *
  211. * If an ISP database is found, ISP information is supported.
  212. *
  213. * @return array
  214. */
  215. public function getSupportedLocationInfo()
  216. {
  217. $result = array();
  218. // country & continent info always available
  219. $result[self::CONTINENT_CODE_KEY] = true;
  220. $result[self::CONTINENT_NAME_KEY] = true;
  221. $result[self::COUNTRY_CODE_KEY] = true;
  222. $result[self::COUNTRY_NAME_KEY] = true;
  223. $locationGeoIp = $this->getGeoIpInstance($key = 'loc');
  224. if ($locationGeoIp) {
  225. switch ($locationGeoIp->databaseType) {
  226. case GEOIP_CITY_EDITION_REV0: // city database type
  227. case GEOIP_CITY_EDITION_REV1:
  228. case GEOIP_CITYCOMBINED_EDITION:
  229. $result[self::REGION_CODE_KEY] = true;
  230. $result[self::REGION_NAME_KEY] = true;
  231. $result[self::CITY_NAME_KEY] = true;
  232. $result[self::AREA_CODE_KEY] = true;
  233. $result[self::LATITUDE_KEY] = true;
  234. $result[self::LONGITUDE_KEY] = true;
  235. $result[self::POSTAL_CODE_KEY] = true;
  236. break;
  237. case GEOIP_REGION_EDITION_REV0: // region database type
  238. case GEOIP_REGION_EDITION_REV1:
  239. $result[self::REGION_CODE_KEY] = true;
  240. $result[self::REGION_NAME_KEY] = true;
  241. break;
  242. default: // country or unknown database type
  243. break;
  244. }
  245. }
  246. // check if isp info is available
  247. if ($this->getGeoIpInstance($key = 'isp')) {
  248. $result[self::ISP_KEY] = true;
  249. }
  250. // check of org info is available
  251. if ($this->getGeoIpInstance($key = 'org')) {
  252. $result[self::ORG_KEY] = true;
  253. }
  254. return $result;
  255. }
  256. /**
  257. * Returns information about this location provider. Contains an id, title & description:
  258. *
  259. * array(
  260. * 'id' => 'geoip_php',
  261. * 'title' => '...',
  262. * 'description' => '...'
  263. * );
  264. *
  265. * @return array
  266. */
  267. public function getInfo()
  268. {
  269. $desc = Piwik::translate('UserCountry_GeoIpLocationProviderDesc_Php1') . '<br/><br/>'
  270. . Piwik::translate('UserCountry_GeoIpLocationProviderDesc_Php2',
  271. array('<strong><em>', '</em></strong>', '<strong><em>', '</em></strong>'));
  272. $installDocs = '<em><a target="_blank" href="http://piwik.org/faq/how-to/#faq_163">'
  273. . Piwik::translate('UserCountry_HowToInstallGeoIPDatabases')
  274. . '</em></a>';
  275. $availableDatabaseTypes = array();
  276. if (self::getPathToGeoIpDatabase(array('GeoIPCity.dat', 'GeoLiteCity.dat')) !== false) {
  277. $availableDatabaseTypes[] = Piwik::translate('UserCountry_City');
  278. }
  279. if (self::getPathToGeoIpDatabase(array('GeoIPRegion.dat')) !== false) {
  280. $availableDatabaseTypes[] = Piwik::translate('UserCountry_Region');
  281. }
  282. if (self::getPathToGeoIpDatabase(array('GeoIPCountry.dat')) !== false) {
  283. $availableDatabaseTypes[] = Piwik::translate('UserCountry_Country');
  284. }
  285. if (self::getPathToGeoIpDatabase(array('GeoIPISP.dat')) !== false) {
  286. $availableDatabaseTypes[] = 'ISP';
  287. }
  288. if (self::getPathToGeoIpDatabase(array('GeoIPOrg.dat')) !== false) {
  289. $availableDatabaseTypes[] = Piwik::translate('UserCountry_Organization');
  290. }
  291. $extraMessage = '<strong><em>' . Piwik::translate('General_Note') . '</em></strong>:&nbsp;'
  292. . Piwik::translate('UserCountry_GeoIPImplHasAccessTo') . ':&nbsp;<strong><em>'
  293. . implode(', ', $availableDatabaseTypes) . '</em></strong>.';
  294. return array('id' => self::ID,
  295. 'title' => self::TITLE,
  296. 'description' => $desc,
  297. 'install_docs' => $installDocs,
  298. 'extra_message' => $extraMessage,
  299. 'order' => 2);
  300. }
  301. /**
  302. * Returns a GeoIP instance. Creates it if necessary.
  303. *
  304. * @param string $key 'loc', 'isp' or 'org'. Determines the type of GeoIP database
  305. * to load.
  306. * @return object|false
  307. */
  308. private function getGeoIpInstance($key)
  309. {
  310. if (empty($this->geoIpCache[$key])) {
  311. // make sure region names are loaded & saved first
  312. parent::getRegionNames();
  313. require_once PIWIK_INCLUDE_PATH . '/libs/MaxMindGeoIP/geoipcity.inc';
  314. $pathToDb = self::getPathToGeoIpDatabase($this->customDbNames[$key]);
  315. if ($pathToDb !== false) {
  316. $this->geoIpCache[$key] = geoip_open($pathToDb, GEOIP_STANDARD); // TODO support shared memory
  317. }
  318. }
  319. return empty($this->geoIpCache[$key]) ? false : $this->geoIpCache[$key];
  320. }
  321. }