├── 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 | }
--------------------------------------------------------------------------------