<?php
/**
 * Piwik - Open source web analytics
 * 
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 * 
 * @category Piwik_Plugins
 * @package Piwik_UserCountry
 */

/**
 * Base type for all GeoIP LocationProviders.
 * 
 * @package Piwik_UserCountry
 */
abstract class Piwik_UserCountry_LocationProvider_GeoIp extends Piwik_UserCountry_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))
		{
			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';

