├── geoip └── ip2country │ └── ip2country.dat ├── registration.php ├── etc ├── module.xml ├── config.xml ├── di.xml └── adminhtml │ └── system.xml ├── composer.json ├── Test └── Unit │ ├── Helper │ └── DataTest.php │ └── Plugin │ └── StoreTest.php ├── README.md ├── Plugin └── FrontControllerInterface.php └── Helper ├── Data.php └── Ip2Country.php /geoip/ip2country/ip2country.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapagain/auto-currency-switcher-2/master/geoip/ip2country/ip2country.dat -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapagain/magento2-autocurrency", 3 | "description": "Automatic Currency Switcher Extension for Magento 2", 4 | "type": "magento2-module", 5 | "version": "2.1.2", 6 | "keywords": ["magento", "currency", "switcher"], 7 | "license": [ 8 | "OSL-3.0", 9 | "AFL-3.0" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Mukesh Chapagain", 14 | "homepage": "http://blog.chapagain.com.np", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "~7.0.13||~7.1.0||~7.2.0||~7.3.0", 20 | "magento/module-config": "*", 21 | "magento/module-store": "*", 22 | "magento/framework": "~100.1 || ~101.0 || ~102.0" 23 | }, 24 | "autoload": { 25 | "files": [ 26 | "registration.php" 27 | ], 28 | "psr-4": { 29 | "Chapagain\\AutoCurrency\\": "" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | chapagain_tab 11 | Chapagain_AutoCurrency::config 12 | 13 | 14 | 15 | 16 | Magento\Config\Model\Config\Source\Yesno 17 | 18 | 19 | 20 | 21 | 22 | 23 | Magento\Config\Model\Config\Source\Yesno 24 | When available, it will use CF-Connecting-IP header instead of using $_SERVER['REMOTE_ADDR'] 25 | 26 | 27 | 28 | Magento\Config\Model\Config\Source\Yesno 29 | When available, it will use Cf-IpCountry to detect user's country, instead of ip2country database. 30 | 31 | 32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /Test/Unit/Helper/DataTest.php: -------------------------------------------------------------------------------- 1 | scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) 36 | ->disableOriginalConstructor() 37 | ->getMock(); 38 | 39 | $this->ip2CountryMock = $this->getMockBuilder(Ip2Country::class) 40 | ->disableOriginalConstructor() 41 | ->getMock(); 42 | 43 | $this->data = new Data($this->scopeConfigMock, $this->ip2CountryMock); 44 | } 45 | 46 | /** 47 | * @test 48 | * Test if the helper class exists 49 | */ 50 | public function testInit() 51 | { 52 | $this->assertInstanceOf(Data::class, $this->data); 53 | } 54 | 55 | /** 56 | * @test 57 | * Test if the module is enabled 58 | */ 59 | public function testIsEnabled() 60 | { 61 | $this->scopeConfigMock->expects($this->once()) 62 | ->method('getValue') 63 | ->with(Data::XML_PATH_AUTOCURRENCY_ENABLED, ScopeInterface::SCOPE_STORE) 64 | ->will($this->returnValue(true)); 65 | 66 | $this->assertTrue((bool)$this->data->isEnabled()); 67 | } 68 | 69 | /** 70 | * @test 71 | * @dataProvider providerTestCheckValidIp 72 | */ 73 | public function TestCheckValidIp($ip, $expectedResult) 74 | { 75 | $this->assertSame($expectedResult, $this->data->checkValidIp($ip)); 76 | } 77 | 78 | /** 79 | * @test 80 | * @dataProvider providerTestCheckIpv6 81 | */ 82 | public function TestCheckIpv6($ip, $expectedResult) 83 | { 84 | $this->assertSame($expectedResult, $this->data->checkIpv6($ip)); 85 | } 86 | 87 | public function providerTestCheckValidIp() 88 | { 89 | return [ 90 | ['128.199.105.86', true], 91 | ['104.131.166.160', true], 92 | ['2002:4559:1FE2::4559:1FE2', true], 93 | ['FE80:0000:0000:0000:0202:B3FF:FE1E:8329', true] 94 | ]; 95 | } 96 | 97 | public function providerTestCheckIpv6() 98 | { 99 | return [ 100 | ['128.199.105.86', false], 101 | ['104.131.166.160', false], 102 | ['2002:4559:1FE2::4559:1FE2', true], 103 | ['FE80:0000:0000:0000:0202:B3FF:FE1E:8329', true] 104 | ]; 105 | } 106 | } -------------------------------------------------------------------------------- /Test/Unit/Plugin/StoreTest.php: -------------------------------------------------------------------------------- 1 | dataMock = $this->getMockBuilder(Data::class) 39 | ->disableOriginalConstructor() 40 | // ->setMethods(null) # all methods of the class run actual code 41 | // ->setMethods([]) # all methods of the class returns null 42 | ->setMethods(['getIpAddress', 'loadIp2Country']) # all methods of the class run actual code except methods in the array which returns null 43 | ->getMock(); 44 | 45 | $currencyMock = $this->getMockBuilder(Currency::class) 46 | ->disableOriginalConstructor() 47 | ->getMock(); 48 | 49 | $scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) 50 | ->disableOriginalConstructor() 51 | ->getMock(); 52 | 53 | $componentRegistrar = $this->getMockBuilder(ComponentRegistrar::class) 54 | ->disableOriginalConstructor() 55 | ->setMethods(null) # all methods of the class run actual code 56 | ->getMock(); 57 | 58 | $this->store = new Store($this->dataMock, $currencyMock); 59 | $this->ip2Country = new Ip2Country($componentRegistrar); 60 | } 61 | 62 | /** 63 | * Test if the model class exists 64 | */ 65 | public function testInit() 66 | { 67 | $this->assertInstanceOf(Store::class, $this->store); 68 | } 69 | 70 | /** 71 | * @test 72 | * @dataProvider providerTestGetCurrencyCodeIp2Country 73 | */ 74 | public function TestGetCurrencyCodeIp2Country($ip, $expectedResult) 75 | { 76 | $this->dataMock->expects($this->any()) 77 | ->method('getIpAddress') 78 | ->willReturn($ip); 79 | 80 | $this->ip2Country->preload(); 81 | $this->dataMock->expects($this->any()) 82 | ->method('loadIp2Country') 83 | ->willReturn($this->ip2Country); 84 | 85 | $defaultCurrencyCode = self::DEFAULT_CURRENCY_CODE; 86 | $code = $this->store->getCurrencyCodeIp2Country($defaultCurrencyCode); 87 | $this->assertSame($expectedResult, $code); 88 | } 89 | 90 | public function providerTestGetCurrencyCodeIp2Country() 91 | { 92 | return [ 93 | ['128.199.105.86', 'GBP'], 94 | ['104.131.166.160', 'USD'], 95 | ['146.185.155.141', 'EUR'], 96 | ['127.0.0.1', self::DEFAULT_CURRENCY_CODE] 97 | ]; 98 | } 99 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Currency Switcher 2 - Magento 2 Extension 2 | 3 | Automatically switches shop's currency to visitor's local currency - "Magento 2" Extension 4 | 5 | Auto Currency extension tracks visitor's IP address and automatically changes the store currency to the visitor's location currency. Visitor can switch to his/her desired currency at any time. 6 | 7 | This extension uses `Webnet77's Ip2Country` IP Address databases for IP Address lookup. 8 | 9 | ## Prerequisite ## 10 | 11 | #### Enable Multiple Currency on your Magento 2 Store 12 | 13 | 1. Login to Magento 2 Admin 14 | 2. Go to `STORES -> Configuration -> GENERAL -> Currency Setup -> Currency Options` 15 | 3. In `Allowed Currencies` box, select the currencies that you want to enable on your site/store 16 | 4. Now, go to `STORES -> Currency Rates` 17 | 5. Import currency rates by clicking the Import button, Or add the rates manually 18 | 6. Then, click the `Save Currency Rates` button 19 | 20 | ## Installation ## 21 | 22 | #### Manual Installation 23 | 24 | 1. The module's files should be placed in folder: `app/code/Chapagain/AutoCurrency` 25 | 2. Open terminal/command-prompt 26 | 3. Go to your Magento website’s root directory with the following command: 27 | - `cd /path/to/your/magento/root/directory` 28 | 4. Enable the module and clear static content with the following command: 29 | - `php bin/magento module:enable Chapagain_AutoCurrency –clear-static-content` 30 | 5. Do setup upgrade with the following command: 31 | - `php bin/magento setup:upgrade` 32 | 33 | #### Composer Installation 34 | 1. Go to your Magento website’s root directory with the following command: 35 | - `cd /path/to/your/magento/root/directory` 36 | 2. Run the following command: 37 | - `composer require chapagain/magento2-autocurrency` 38 | 3. Enable the module and clear static content with the following command: 39 | - `php bin/magento module:enable Chapagain_AutoCurrency –clear-static-content` 40 | 4. Do setup upgrade with the following command: 41 | - `php bin/magento setup:upgrade` 42 | 43 | #### Configuration Settings 44 | 45 | 1. Login to your Magento site's admin 46 | 2. Go to `STORES → Settings → Configuration` page 47 | 3. On left sidebar, click on `CHAPAGAIN EXTENSIONS → Auto Currency` menu 48 | 4. From there, you can Enable/Disable the module. The module is enabled by default. 49 | 50 | ## Updating GeoIP Databases ## 51 | 52 | The GeoIP database should be updated from time to time in order to make this extension work accurately. 53 | 54 | 1. Download [IPV4 CSV](http://software77.net/geo-ip/) file 55 | 2. Extract the file. This will extract `IPtoCountry.csv` file. 56 | 3. Use [Ip2Country lookup classes for PHP](https://github.com/mgefvert/Ip2Country) to create binary-optimized version of the csv file. 57 | 4. Upload the binary file (`.dat` file) to your `[Module Folder]/geoip/ip2country/` folder. 58 | 59 | ## Links 60 | 61 | - [BLOG: Auto Currency Switcher 2: Magento 2 Extension [FREE]](http://blog.chapagain.com.np/auto-currency-switcher-2-magento-2-extension-free/) 62 | 63 | - [GitHub Repository: Magento 1.x - Auto Currency Switcher](https://github.com/chapagain/auto-currency-switcher) 64 | 65 | - [BLOG: Magento 1.x Extension: Auto Currency Switcher](http://blog.chapagain.com.np/magento-extension-auto-currency-switcher-free/) 66 | -------------------------------------------------------------------------------- /Plugin/FrontControllerInterface.php: -------------------------------------------------------------------------------- 1 | helper = $helper; 33 | $this->currency = $currency; 34 | $this->storeManager = $storeManager; 35 | } 36 | 37 | /** 38 | * Update current store currency code 39 | * 40 | * @param \Magento\Framework\App\FrontControllerInterface $subject 41 | * @param callable $proceed 42 | * @param \Magento\Framework\App\RequestInterface $request 43 | * 44 | * @return \Magento\Framework\Controller\ResultInterface|\Magento\Framework\App\Response\Http 45 | */ 46 | public function aroundDispatch( 47 | \Magento\Framework\App\FrontControllerInterface $subject, 48 | \Closure $proceed, 49 | \Magento\Framework\App\RequestInterface $request 50 | ) { 51 | if ($this->helper->isEnabled()) { 52 | $currentCurrency = $this->storeManager->getStore()->getCurrentCurrencyCode(); 53 | $newCurrency = $this->getCurrencyCodeByIp($currentCurrency); 54 | if ($currentCurrency !== $newCurrency) { 55 | $this->storeManager->getStore()->setCurrentCurrencyCode($newCurrency); 56 | unset($_COOKIE[\Magento\Framework\App\Response\Http::COOKIE_VARY_STRING]); 57 | } 58 | } 59 | return $proceed($request); 60 | } 61 | 62 | /** 63 | * Get Currency code by IP Address 64 | * 65 | * @param string $result Currency Code 66 | * @return string $currencyCode 67 | */ 68 | public function getCurrencyCodeByIp($result = '') 69 | { 70 | $currencyCode = $this->getCurrencyCodeIp2Country($result); 71 | // if currencyCode is not present in allowedCurrencies 72 | // then return the default currency code 73 | $allowedCurrencies = $this->currency->getConfigAllowCurrencies(); 74 | //$allowedCurrencies = $this->currencyFactory->getConfigAllowCurrencies(); 75 | if (!in_array($currencyCode, $allowedCurrencies)) { 76 | return $result; 77 | } 78 | return $currencyCode; 79 | } 80 | 81 | /** 82 | * Get Currency code by IP Address 83 | * Using Ip2Country Database 84 | * 85 | * @param string $result Currency Code 86 | * @return string $currencyCode 87 | */ 88 | public function getCurrencyCodeIp2Country($result = '') 89 | { 90 | // load Ip2Country database 91 | $ipc = $this->helper->loadIp2Country(); 92 | // get IP Address 93 | $ipAddress = $this->helper->getIpAddress(); 94 | // additional valid ip check 95 | // because Ip2Country generates error for invalid IP address 96 | if (!$this->helper->checkValidIp($ipAddress)) { 97 | return null; 98 | } 99 | $countryCode = $this->helper->useCloudflareCountry() ? $_SERVER['HTTP_CF_IPCOUNTRY'] : $ipc->lookup($ipAddress); 100 | // return default currency code when country code is ZZ 101 | // i.e. if browsed in localhost / personal computer 102 | if ($countryCode == 'ZZ') { 103 | $currencyCode = $result; 104 | } else { 105 | $currencyCode = $this->helper->getCurrencyByCountry($countryCode); 106 | } 107 | return $currencyCode; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Helper/Data.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 28 | $this->ip2Country = $ip2Country; 29 | } 30 | 31 | /** 32 | * Check if the module is enabled 33 | * 34 | * @return boolean 0 or 1 35 | */ 36 | public function isEnabled() 37 | { 38 | $storeScope = \Magento\Store\Model\ScopeInterface::SCOPE_STORE; 39 | return $this->scopeConfig->getValue(self::XML_PATH_AUTOCURRENCY_ENABLED, $storeScope); 40 | } 41 | 42 | /** 43 | * Checks if we should use $_SERVER[CF-Connecting-IP] from Cloudflare 44 | * @return bool 45 | */ 46 | public function useCloudflareIpHeader() 47 | { 48 | $storeScope = \Magento\Store\Model\ScopeInterface::SCOPE_STORE; 49 | $isAvailable = isset($_SERVER['HTTP_CF_CONNECTING_IP']); 50 | return (bool)$this->scopeConfig->getValue(self::XML_PATH_CLOUDFLARE_USE_CONNECTING_IP, $storeScope) 51 | && $isAvailable; 52 | } 53 | 54 | /** 55 | * Checks if we should use $_SERVER[Cf-IpCountry] from Cloudflare 56 | * @return bool 57 | */ 58 | public function useCloudflareCountry() 59 | { 60 | $storeScope = \Magento\Store\Model\ScopeInterface::SCOPE_STORE; 61 | $isAvailable = isset($_SERVER['HTTP_CF_IPCOUNTRY']); 62 | return (bool)$this->scopeConfig->getValue(self::XML_PATH_CLOUDFLARE_USE_IPCOUNTRY, $storeScope) 63 | && $isAvailable; 64 | } 65 | 66 | /** 67 | * Get IP Address 68 | * 69 | * @return string 70 | */ 71 | public function getIpAddress() 72 | { 73 | // http://www.xroxy.com/proxylist.php?country=GB&sort=ip 74 | //return '128.199.105.86'; // GB/UK 75 | //return '104.131.166.160'; // US 76 | //return '146.185.155.141'; // NL 77 | //return '103.100.83.253'; // IN 78 | //return '203.78.162.156'; // NP 79 | if ($this->useCloudflareIpHeader()) { 80 | return $_SERVER['HTTP_CF_CONNECTING_IP']; 81 | } 82 | 83 | return $_SERVER['REMOTE_ADDR']; 84 | } 85 | 86 | /** 87 | * Check whether the given IP Address is valid 88 | * Returns true for both IPv4 and IPv6 addresses 89 | * 90 | * @param string IP Address 91 | * @return boolean True/False 92 | */ 93 | public function checkValidIp($ip) 94 | { 95 | if(!filter_var($ip, FILTER_VALIDATE_IP)) { 96 | return false; 97 | } 98 | return true; 99 | } 100 | 101 | /** 102 | * Check whether the given IP Address is IPv6 103 | * Returns true for IPv6 addresses only 104 | * Returns false for IPv4 addresses 105 | * 106 | * @param string IP Address 107 | * @return boolean True/False 108 | */ 109 | public function checkIpv6($ip) 110 | { 111 | if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { 112 | return false; 113 | } 114 | return true; 115 | } 116 | 117 | /** 118 | * Include IP2Country.php file 119 | * and load ip2country.dat file 120 | * 121 | */ 122 | public function loadIp2Country() 123 | { 124 | if ($this->useCloudflareCountry()) { 125 | $this->ip2Country->preload(); 126 | } 127 | 128 | return $this->ip2Country; 129 | } 130 | 131 | /** 132 | * Get Currency code by Country code 133 | * 134 | * @return string 135 | */ 136 | public function getCurrencyByCountry($countryCode) 137 | { 138 | $map = array( '' => '', 139 | "EU" => "EUR", "AD" => "EUR", "AE" => "AED", "AF" => "AFN", "AG" => "XCD", "AI" => "XCD", 140 | "AL" => "ALL", "AM" => "AMD", "CW" => "ANG", "AO" => "AOA", "AQ" => "AQD", "AR" => "ARS", "AS" => "EUR", 141 | "AT" => "EUR", "AU" => "AUD", "AW" => "AWG", "AZ" => "AZN", "BA" => "BAM", "BB" => "BBD", 142 | "BD" => "BDT", "BE" => "EUR", "BF" => "XOF", "BG" => "BGL", "BH" => "BHD", "BI" => "BIF", 143 | "BJ" => "XOF", "BM" => "BMD", "BN" => "BND", "BO" => "BOB", "BR" => "BRL", "BS" => "BSD", 144 | "BT" => "BTN", "BV" => "NOK", "BW" => "BWP", "BY" => "BYR", "BZ" => "BZD", "CA" => "CAD", 145 | "CC" => "AUD", "CD" => "CDF", "CF" => "XAF", "CG" => "XAF", "CH" => "CHF", "CI" => "XOF", 146 | "CK" => "NZD", "CL" => "CLP", "CM" => "XAF", "CN" => "CNY", "CO" => "COP", "CR" => "CRC", 147 | "CU" => "CUP", "CV" => "CVE", "CX" => "AUD", "CY" => "EUR", "CZ" => "CZK", "DE" => "EUR", 148 | "DJ" => "DJF", "DK" => "DKK", "DM" => "XCD", "DO" => "DOP", "DZ" => "DZD", "EC" => "ECS", 149 | "EE" => "EEK", "EG" => "EGP", "EH" => "MAD", "ER" => "ETB", "ES" => "EUR", "ET" => "ETB", 150 | "FI" => "EUR", "FJ" => "FJD", "FK" => "FKP", "FM" => "USD", "FO" => "DKK", "FR" => "EUR", "SX" => "ANG", 151 | "GA" => "XAF", "GB" => "GBP", "GD" => "XCD", "GE" => "GEL", "GF" => "EUR", "GH" => "GHS", 152 | "GI" => "GIP", "GL" => "DKK", "GM" => "GMD", "GN" => "GNF", "GP" => "EUR", "GQ" => "XAF", 153 | "GR" => "EUR", "GS" => "GBP", "GT" => "GTQ", "GU" => "USD", "GW" => "XOF", "GY" => "GYD", 154 | "HK" => "HKD", "HM" => "AUD", "HN" => "HNL", "HR" => "HRK", "HT" => "HTG", "HU" => "HUF", 155 | "ID" => "IDR", "IE" => "EUR", "IL" => "ILS", "IN" => "INR", "IO" => "USD", "IQ" => "IQD", 156 | "IR" => "IRR", "IS" => "ISK", "IT" => "EUR", "JM" => "JMD", "JO" => "JOD", "JP" => "JPY", 157 | "KE" => "KES", "KG" => "KGS", "KH" => "KHR", "KI" => "AUD", "KM" => "KMF", "KN" => "XCD", 158 | "KP" => "KPW", "KR" => "KRW", "KW" => "KWD", "KY" => "KYD", "KZ" => "KZT", "LA" => "LAK", 159 | "LB" => "LBP", "LC" => "XCD", "LI" => "CHF", "LK" => "LKR", "LR" => "LRD", "LS" => "LSL", 160 | "LT" => "LTL", "LU" => "EUR", "LV" => "LVL", "LY" => "LYD", "MA" => "MAD", "MC" => "EUR", 161 | "MD" => "MDL", "MG" => "MGF", "MH" => "USD", "MK" => "MKD", "ML" => "XOF", "MM" => "MMK", 162 | "MN" => "MNT", "MO" => "MOP", "MP" => "USD", "MQ" => "EUR", "MR" => "MRO", "MS" => "XCD", 163 | "MT" => "EUR", "MU" => "MUR", "MV" => "MVR", "MW" => "MWK", "MX" => "MXN", "MY" => "MYR", 164 | "MZ" => "MZN", "NA" => "NAD", "NC" => "XPF", "NE" => "XOF", "NF" => "AUD", "NG" => "NGN", 165 | "NI" => "NIO", "NL" => "EUR", "NO" => "NOK", "NP" => "NPR", "NR" => "AUD", "NU" => "NZD", 166 | "NZ" => "NZD", "OM" => "OMR", "PA" => "PAB", "PE" => "PEN", "PF" => "XPF", "PG" => "PGK", 167 | "PH" => "PHP", "PK" => "PKR", "PL" => "PLN", "PM" => "EUR", "PN" => "NZD", "PR" => "USD", "PS" => "ILS", "PT" => "EUR", 168 | "PW" => "USD", "PY" => "PYG", "QA" => "QAR", "RE" => "EUR", "RO" => "RON", "RU" => "RUB", 169 | "RW" => "RWF", "SA" => "SAR", "SB" => "SBD", "SC" => "SCR", "SD" => "SDD", "SE" => "SEK", 170 | "SG" => "SGD", "SH" => "SHP", "SI" => "EUR", "SJ" => "NOK", "SK" => "SKK", "SL" => "SLL", 171 | "SM" => "EUR", "SN" => "XOF", "SO" => "SOS", "SR" => "SRG", "ST" => "STD", "SV" => "SVC", 172 | "SY" => "SYP", "SZ" => "SZL", "TC" => "USD", "TD" => "XAF", "TF" => "EUR", "TG" => "XOF", 173 | "TH" => "THB", "TJ" => "TJS", "TK" => "NZD", "TM" => "TMM", "TN" => "TND", "TO" => "TOP", "TL" => "USD", 174 | "TR" => "TRY", "TT" => "TTD", "TV" => "AUD", "TW" => "TWD", "TZ" => "TZS", "UA" => "UAH", 175 | "UG" => "UGX", "UM" => "USD", "US" => "USD", "UY" => "UYU", "UZ" => "UZS", "VA" => "EUR", 176 | "VC" => "XCD", "VE" => "VEF", "VG" => "USD", "VI" => "USD", "VN" => "VND", "VU" => "VUV", 177 | "WF" => "XPF", "WS" => "EUR", "YE" => "YER", "YT" => "EUR", "RS" => "RSD", 178 | "ZA" => "ZAR", "ZM" => "ZMK", "ME" => "EUR", "ZW" => "ZWD", 179 | "AX" => "EUR", "GG" => "GBP", "IM" => "GBP", 180 | "JE" => "GBP", "BL" => "EUR", "MF" => "EUR", "BQ" => "USD", "SS" => "SSP" 181 | ); 182 | 183 | return $map[$countryCode]; 184 | } 185 | } -------------------------------------------------------------------------------- /Helper/Ip2Country.php: -------------------------------------------------------------------------------- 1 | moduleReader->getModuleDir(\Magento\Framework\Module\Dir::MODULE_VIEW_DIR,'Vendor_Module'); 60 | $this->componentRegistrar = $componentRegistrar; 61 | $path = $this->componentRegistrar->getPath(ComponentRegistrar::MODULE, self::MODULE_NAME); 62 | $datafile = $path . self::BINARY_FILE; 63 | 64 | $this->fp = fopen($datafile, 'r'); 65 | 66 | $header = fread($this->fp, 8); 67 | list(, $indexlen, $this->datalen) = unpack('L2', $header); 68 | $this->offset = 8 + $indexlen; 69 | 70 | $this->index = array_fill_keys(range(0, 8191), null); 71 | 72 | $index = str_split(fread($this->fp, $indexlen), 12); 73 | foreach($index as $ix) 74 | $this->index[self::ipgroup($ix)] .= $ix; 75 | 76 | $lastv = null; 77 | foreach($this->index as $k => &$v) 78 | if ($v) 79 | $lastv = $v; 80 | else 81 | $v = $lastv; 82 | } 83 | 84 | /** 85 | * Destroys the ip 2 country instance. 86 | */ 87 | public function __destruct() 88 | { 89 | if ($this->fp) 90 | fclose($this->fp); 91 | } 92 | 93 | /** 94 | * Get the group of an IP address by returning the 13 high bits 95 | */ 96 | public static function ipgroup($ipbin) 97 | { 98 | return (ord($ipbin[0]) << 8 | ord($ipbin[1])) >> 3; 99 | } 100 | 101 | /** 102 | * Preloads the entire data file into memory for faster lookups of many IP 103 | * addresses 104 | */ 105 | public function preload() 106 | { 107 | if ($this->fp == null) 108 | return; 109 | 110 | fseek($this->fp, $this->offset); 111 | $data = fread($this->fp, $this->datalen); 112 | 113 | foreach($this->index as $index) 114 | foreach(str_split($index, 12) as $ix) 115 | { 116 | list(, , $pos, $len) = unpack('L3', $ix); 117 | if (!isset($this->data[$pos])) 118 | $this->data[$pos] = substr($data, $pos, $len + 6); // Always one extra record, for upper bound 119 | } 120 | 121 | fclose($this->fp); 122 | $this->fp = null; 123 | } 124 | 125 | /** 126 | * Binary search function that searches a string with records for a match. 127 | * Requires that the array is sorted. Since we're operating on 32-bit 128 | * IPs, we match the first four bytes of every string and the 129 | * rest is just payload. Returns the entry "n" for which the relation 130 | * 32bit($data[n]) <= $str < 32bit($data[n+1]) 131 | * is true. 132 | * 133 | * Always tries to return 4 extra bytes from the next record, for an upper 134 | * bound. So if $reclen is 12, it tries to return 16 bytes. 135 | */ 136 | private function findRecord($data, $str, $reclen) 137 | { 138 | $l = 0; 139 | $count = strlen($data) / $reclen; 140 | $h = $count - 1; 141 | while ($l <= $h) 142 | { 143 | $i = ($l + $h) >> 1; 144 | 145 | // Get the current record (i) and compare 146 | $rec = substr($data, $i * $reclen, $reclen + 4); 147 | $c = strcmp($str, substr($rec, 0, 4)); 148 | 149 | // Equal? I guess we found it. 150 | if ($c == 0) 151 | return $rec; 152 | // If it's more, then compare with the next record to see if that one is less 153 | else if ($c > 0) 154 | { 155 | // If this was the last record, then return it 156 | if ($i+1 >= $count) 157 | return $rec; 158 | 159 | $rec1 = substr($data, ($i+1)*$reclen, $reclen + 4); 160 | $c1 = strcmp($str, substr($rec1, 0, 4)); 161 | 162 | // Did we stumble upon the right one? 163 | if ($c1 == 0) 164 | // Oops. Equal with the next one. Just return. 165 | return $rec1; 166 | else if ($c1 < 0) 167 | // Yep, this is the right one. 168 | return $rec; 169 | 170 | $l = $i + 1; 171 | } 172 | else 173 | $h = $i - 1; 174 | } 175 | 176 | // Never supposed to end up here - something is wrong. 177 | throw new Exception('Binary find failure'); 178 | } 179 | 180 | /** 181 | * Find the index given by the IP address. 182 | */ 183 | private function findIndex($ipbin) 184 | { 185 | $group = self::ipgroup($ipbin); 186 | 187 | $ix = $this->index[$group]; 188 | if (strcmp($ipbin, substr($ix, 0, 4)) < 0 && $group > 0) 189 | $ix = $this->index[$group-1]; 190 | 191 | if (strlen($ix) > 12) 192 | $ix = $this->findRecord($ix, $ipbin, 12); 193 | 194 | return $ix; 195 | } 196 | 197 | /** 198 | * Load the specific data chunk from the main data file and save it 199 | * to the data cache. 200 | */ 201 | private function getDataEntry($pos, $len) 202 | { 203 | // If it doesn't exist in the cache, load it 204 | if (!isset($this->data[$pos])) 205 | { 206 | fseek($this->fp, $pos + $this->offset); 207 | $this->data[$pos] = fread($this->fp, $len + 6); // Always one extra record, for upper bound 208 | } 209 | 210 | return $this->data[$pos]; 211 | } 212 | 213 | /** 214 | * Lookup a 4-byte binary IP string and return the two-letter ISO country. 215 | */ 216 | public function lookupbin($ipbin) 217 | { 218 | if (strlen($this->lastentry) == 10 && strcmp($ipbin, substr($this->lastentry, 0, 4)) >= 0 && strcmp($ipbin, substr($this->lastentry, 6, 4)) < 0) 219 | return substr($this->lastentry, 4, 2); 220 | 221 | list(, , $pos, $len) = unpack('L3', $this->findIndex($ipbin)); 222 | $data = $this->getDataEntry($pos, $len); 223 | 224 | $this->lastentry = $this->findRecord($data, $ipbin, 6); 225 | 226 | return substr($this->lastentry, 4, 2); 227 | } 228 | 229 | /** 230 | * Lookup an IP address and return the two-letter ISO country. 231 | */ 232 | public function lookup($ip) 233 | { 234 | return $this->lookupbin(inet_pton($ip)); 235 | } 236 | } --------------------------------------------------------------------------------