├── LICENSE
├── README.md
├── composer.json
├── config
└── geoip.php
├── phpcs.xml
├── phpstan.neon
├── resources
└── geoip.mmdb
└── src
├── Cache.php
├── Console
├── Clear.php
└── Update.php
├── Contracts
└── ServiceInterface.php
├── Facades
└── GeoIP.php
├── GeoIP.php
├── GeoIPServiceProvider.php
├── Location.php
├── Services
├── AbstractService.php
├── IPApi.php
├── IPData.php
├── IPFinder.php
├── IPGeoLocation.php
├── MaxMindDatabase.php
└── MaxMindWebService.php
├── Support
├── Currencies.php
└── HttpClient.php
└── helpers.php
/LICENSE:
--------------------------------------------------------------------------------
1 | The BSD 2-Clause License
2 | Copyright (c) 2013-2020, Daniel Stainback
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10 |
11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GeoIP for Laravel
2 |
3 | [](https://packagist.org/packages/torann/geoip)
4 | [](https://packagist.org/packages/torann/geoip)
5 | [](https://www.patreon.com/torann)
6 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4CJA2A97NPYVU)
7 |
8 | Determine the geographical location and currency of website visitors based on their IP addresses.
9 |
10 | - [GeoIP for Laravel on Packagist](https://packagist.org/packages/torann/geoip)
11 | - [GeoIP for Laravel on GitHub](https://github.com/Torann/laravel-geoip)
12 | - [Upgrade Guides](http://lyften.com/projects/laravel-geoip/doc/upgrade.html)
13 |
14 | ## v3.0.8 Possible Breaking Change
15 |
16 | Out of the box the system no longer comes with a service pre-configured. Which will cause an error for those who did **NOT** previously publish the configurtion file. Follow the [getting started guide](https://lyften.com/projects/laravel-geoip/doc/) on how to publish the config and how to set up a service.
17 |
18 | Sorry for this inconvenience, but MaxMind made me remove the free service that was the default. If the config file was published prior to this change, you should not been affected.
19 |
20 | ## Official Documentation
21 |
22 | Documentation for the package can be found on [Lyften.com](http://lyften.com/projects/laravel-geoip/).
23 |
24 | ## Contributions
25 |
26 | Many people have contributed to project since its inception.
27 |
28 | Thanks to:
29 |
30 | - [Dwight Watson](https://github.com/dwightwatson)
31 | - [nikkiii](https://github.com/nikkiii)
32 | - [jeffhennis](https://github.com/jeffhennis)
33 | - [max-kovpak](https://github.com/max-kovpak)
34 | - [dotpack](https://github.com/dotpack)
35 | - [Jess Archer](https://github.com/jessarcher)
36 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "torann/geoip",
3 | "description": "Support for multiple Geographical Location services.",
4 | "keywords": [
5 | "laravel",
6 | "geoip",
7 | "geographical",
8 | "location",
9 | "geolocation",
10 | "IP API",
11 | "infoDB"
12 | ],
13 | "license": "BSD-2-Clause",
14 | "authors": [
15 | {
16 | "name": "Daniel Stainback",
17 | "email": "torann@gmail.com"
18 | }
19 | ],
20 | "require": {
21 | "php": "^8.0|^8.1|^8.2|^8.3",
22 | "illuminate/cache": "^8.0|^9.0|^10.0|^11.0|^12.0",
23 | "illuminate/console": "^8.0|^9.0|^10.0|^11.0|^12.0",
24 | "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0"
25 | },
26 | "suggest": {
27 | "geoip2/geoip2": "Required to use the MaxMind database or web service with GeoIP (~2.1).",
28 | "monolog/monolog": "Allows for storing location not found errors to the log"
29 | },
30 | "require-dev": {
31 | "phpunit/phpunit": "^8.0|^9.0|^10.0|^11.0",
32 | "mockery/mockery": "^1.3",
33 | "geoip2/geoip2": "~2.1|~3.0",
34 | "vlucas/phpdotenv": "^5.0",
35 | "phpstan/phpstan": "^0.12.14|^1.9",
36 | "squizlabs/php_codesniffer": "^3.5"
37 | },
38 | "autoload": {
39 | "files": [
40 | "src/helpers.php"
41 | ],
42 | "psr-4": {
43 | "Torann\\GeoIP\\": "src/"
44 | }
45 | },
46 | "autoload-dev": {
47 | "files": [
48 | "tests/TestFunctions.php"
49 | ],
50 | "psr-4": {
51 | "Torann\\GeoIP\\Tests\\": "tests/"
52 | }
53 | },
54 | "extra": {
55 | "branch-alias": {
56 | "dev-master": "1.0-dev"
57 | },
58 | "laravel": {
59 | "providers": [
60 | "Torann\\GeoIP\\GeoIPServiceProvider"
61 | ],
62 | "aliases": {
63 | "GeoIP": "Torann\\GeoIP\\Facades\\GeoIP"
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/config/geoip.php:
--------------------------------------------------------------------------------
1 | true,
16 |
17 | /*
18 | |--------------------------------------------------------------------------
19 | | Include Currency in Results
20 | |--------------------------------------------------------------------------
21 | |
22 | | When enabled the system will do it's best in deciding the user's currency
23 | | by matching their ISO code to a preset list of currencies.
24 | |
25 | */
26 |
27 | 'include_currency' => true,
28 |
29 | /*
30 | |--------------------------------------------------------------------------
31 | | Default Service
32 | |--------------------------------------------------------------------------
33 | |
34 | | Here you may specify the default storage driver that should be used
35 | | by the framework using the services listed below.
36 | |
37 | */
38 |
39 | 'service' => null,
40 |
41 | /*
42 | |--------------------------------------------------------------------------
43 | | Storage Specific Configuration
44 | |--------------------------------------------------------------------------
45 | |
46 | | Here you may configure as many storage drivers as you wish.
47 | |
48 | */
49 |
50 | 'services' => [
51 |
52 | 'maxmind_database' => [
53 | 'class' => \Torann\GeoIP\Services\MaxMindDatabase::class,
54 | 'database_path' => storage_path('app/geoip.mmdb'),
55 | 'update_url' => sprintf('https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz', env('MAXMIND_LICENSE_KEY')),
56 | 'locales' => ['en'],
57 | ],
58 |
59 | 'maxmind_api' => [
60 | 'class' => \Torann\GeoIP\Services\MaxMindWebService::class,
61 | 'user_id' => env('MAXMIND_USER_ID'),
62 | 'license_key' => env('MAXMIND_LICENSE_KEY'),
63 | 'locales' => ['en'],
64 | ],
65 |
66 | 'ipgeolocation' => [
67 | 'class' => \Torann\GeoIP\Services\IPGeoLocation::class,
68 | 'secure' => true,
69 | 'key' => env('IPGEOLOCATION_KEY'),
70 | 'continent_path' => storage_path('app/continents.json'),
71 | 'lang' => 'en',
72 | ],
73 |
74 | 'ipdata' => [
75 | 'class' => \Torann\GeoIP\Services\IPData::class,
76 | 'key' => env('IPDATA_API_KEY'),
77 | 'secure' => true,
78 | ],
79 |
80 | 'ipfinder' => [
81 | 'class' => \Torann\GeoIP\Services\IPFinder::class,
82 | 'key' => env('IPFINDER_API_KEY'),
83 | 'secure' => true,
84 | 'locales' => ['en'],
85 | ],
86 |
87 | ],
88 |
89 | /*
90 | |--------------------------------------------------------------------------
91 | | Default Cache Driver
92 | |--------------------------------------------------------------------------
93 | |
94 | | Here you may specify the type of caching that should be used
95 | | by the package.
96 | |
97 | | Options:
98 | |
99 | | all - All location are cached
100 | | some - Cache only the requesting user
101 | | none - Disable cached
102 | |
103 | */
104 |
105 | 'cache' => 'all',
106 |
107 | /*
108 | |--------------------------------------------------------------------------
109 | | Cache Tags
110 | |--------------------------------------------------------------------------
111 | |
112 | | Cache tags are not supported when using the file or database cache
113 | | drivers in Laravel. This is done so that only locations can be cleared.
114 | |
115 | */
116 |
117 | 'cache_tags' => ['torann-geoip-location'],
118 |
119 | /*
120 | |--------------------------------------------------------------------------
121 | | Cache Expiration
122 | |--------------------------------------------------------------------------
123 | |
124 | | Define how long cached location are valid.
125 | |
126 | */
127 |
128 | 'cache_expires' => 30,
129 |
130 | /*
131 | |--------------------------------------------------------------------------
132 | | Default Location
133 | |--------------------------------------------------------------------------
134 | |
135 | | Return when a location is not found.
136 | |
137 | */
138 |
139 | 'default_location' => [
140 | 'ip' => '127.0.0.0',
141 | 'iso_code' => 'US',
142 | 'country' => 'United States',
143 | 'city' => 'New Haven',
144 | 'state' => 'CT',
145 | 'state_name' => 'Connecticut',
146 | 'postal_code' => '06510',
147 | 'lat' => 41.31,
148 | 'lon' => -72.92,
149 | 'timezone' => 'America/New_York',
150 | 'continent' => 'NA',
151 | 'default' => true,
152 | 'currency' => 'USD',
153 | ],
154 |
155 | ];
156 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | The PSR2 standard
4 |
5 |
6 | /resources/
7 | /vendor/
8 | /_[a-zA-Z0-9\._]+\.php
9 | /\.[a-zA-Z0-9\._]+\.php
10 | \.git
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | excludes_analyse:
3 | - vendor
4 | - resources
5 | ignoreErrors:
6 | - '#Function config_path not found#'
7 | - '#Function config not found#'
8 | - '#Instantiated class Monolog\\Logger not found#'
9 | - '#Instantiated class Monolog\\Handler\\StreamHandler not found#'
10 | - '#Access to constant ERROR on an unknown class Monolog\\Logger#'
11 |
--------------------------------------------------------------------------------
/resources/geoip.mmdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Torann/laravel-geoip/1ea60c7e1a1608de3885e8cd76389cfe5c8424de/resources/geoip.mmdb
--------------------------------------------------------------------------------
/src/Cache.php:
--------------------------------------------------------------------------------
1 | cache = $tags ? $cache->tags($tags) : $cache;
33 | $this->expires = $expires;
34 | }
35 |
36 | /**
37 | * Get an item from the cache.
38 | *
39 | * @param string $name
40 | *
41 | * @return Location|null
42 | */
43 | public function get($name)
44 | {
45 | $value = $this->cache->get($name);
46 |
47 | return is_array($value)
48 | ? new Location($value)
49 | : null;
50 | }
51 |
52 | /**
53 | * Store an item in cache.
54 | *
55 | * @param string $name
56 | * @param Location $location
57 | *
58 | * @return bool
59 | */
60 | public function set($name, Location $location)
61 | {
62 | return $this->cache->put($name, $location->toArray(), $this->expires);
63 | }
64 |
65 | /**
66 | * Flush cache for tags.
67 | *
68 | * @return bool
69 | */
70 | public function flush()
71 | {
72 | return $this->cache->flush();
73 | }
74 | }
--------------------------------------------------------------------------------
/src/Console/Clear.php:
--------------------------------------------------------------------------------
1 | fire();
31 | }
32 |
33 | /**
34 | * Execute the console command.
35 | *
36 | * @return void
37 | */
38 | public function fire()
39 | {
40 | if ($this->isSupported() === false) {
41 | return $this->output->error('Default cache system does not support tags');
42 | }
43 |
44 | $this->performFlush();
45 | }
46 |
47 | /**
48 | * Is cache flushing supported.
49 | *
50 | * @return bool
51 | */
52 | protected function isSupported()
53 | {
54 | return empty(app('geoip')->config('cache_tags')) === false
55 | && in_array(config('cache.default'), ['file', 'database']) === false;
56 | }
57 |
58 | /**
59 | * Flush the cache.
60 | *
61 | * @return void
62 | */
63 | protected function performFlush()
64 | {
65 | $this->output->write("Clearing cache...");
66 |
67 | app('geoip')->getCache()->flush();
68 |
69 | $this->output->writeln("complete");
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Console/Update.php:
--------------------------------------------------------------------------------
1 | fire();
31 | }
32 |
33 | /**
34 | * Execute the console command.
35 | *
36 | * @return void
37 | */
38 | public function fire()
39 | {
40 | // Get default service
41 | $service = app('geoip')->getService();
42 |
43 | // Ensure the selected service supports updating
44 | if (method_exists($service, 'update') === false) {
45 | $this->info('The current service "' . get_class($service) . '" does not support updating.');
46 |
47 | return;
48 | }
49 |
50 | $this->comment('Updating...');
51 |
52 | // Perform update
53 | if ($result = $service->update()) {
54 | $this->info($result);
55 | }
56 | else {
57 | $this->error('Update failed!');
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Contracts/ServiceInterface.php:
--------------------------------------------------------------------------------
1 | '127.0.0.0',
62 | 'iso_code' => 'US',
63 | 'country' => 'United States',
64 | 'city' => 'New Haven',
65 | 'state' => 'CT',
66 | 'state_name' => 'Connecticut',
67 | 'postal_code' => '06510',
68 | 'lat' => 41.31,
69 | 'lon' => -72.92,
70 | 'timezone' => 'America/New_York',
71 | 'continent' => 'NA',
72 | 'currency' => 'USD',
73 | 'default' => true,
74 | 'cached' => false,
75 | ];
76 |
77 | /**
78 | * Create a new GeoIP instance.
79 | *
80 | * @param array $config
81 | * @param CacheManager $cache
82 | */
83 | public function __construct(array $config, CacheManager $cache)
84 | {
85 | $this->config = $config;
86 |
87 | // Create caching instance
88 | $this->cache = new Cache(
89 | $cache,
90 | $this->config('cache_tags'),
91 | $this->config('cache_expires', 30)
92 | );
93 |
94 | // Set custom default location
95 | $this->default_location = array_merge(
96 | $this->default_location,
97 | $this->config('default_location', [])
98 | );
99 |
100 | // Set IP
101 | $this->remote_ip = $this->default_location['ip'] = $this->getClientIP();
102 | }
103 |
104 | /**
105 | * Get the location from the provided IP.
106 | *
107 | * @param string $ip
108 | *
109 | * @return \Torann\GeoIP\Location
110 | * @throws \Exception
111 | */
112 | public function getLocation($ip = null)
113 | {
114 | // Get location data
115 | $this->location = $this->find($ip);
116 |
117 | // Should cache location
118 | if ($this->shouldCache($this->location, $ip)) {
119 | $this->getCache()->set($ip, $this->location);
120 | }
121 |
122 | return $this->location;
123 | }
124 |
125 | /**
126 | * Find location from IP.
127 | *
128 | * @param string $ip
129 | *
130 | * @return \Torann\GeoIP\Location
131 | * @throws \Exception
132 | */
133 | private function find($ip = null)
134 | {
135 | // If IP not set, user remote IP
136 | $ip = $ip ?: $this->remote_ip;
137 |
138 | // Check cache for location
139 | if ($this->config('cache', 'none') !== 'none' && $location = $this->getCache()->get($ip)) {
140 | $location->cached = true;
141 |
142 | return $location;
143 | }
144 |
145 | // Check if the ip is not local or empty
146 | if ($this->isValid($ip)) {
147 | try {
148 | // Find location
149 | $location = $this->getService()->locate($ip);
150 |
151 | // Set currency if not already set by the service
152 | if (! $location->currency) {
153 | $location->currency = $this->getCurrency($location->iso_code);
154 | }
155 |
156 | // Set default
157 | $location->default = false;
158 |
159 | return $location;
160 | } catch (\Exception $e) {
161 | if ($this->config('log_failures', true) === true) {
162 | $log = new Logger('geoip');
163 | $log->pushHandler(new StreamHandler(storage_path('logs/geoip.log'), Logger::ERROR));
164 | $log->error($e);
165 | }
166 | }
167 | }
168 |
169 | return $this->getService()->hydrate($this->default_location);
170 | }
171 |
172 | /**
173 | * Get the currency code from ISO.
174 | *
175 | * @param string $iso
176 | *
177 | * @return string
178 | */
179 | public function getCurrency($iso)
180 | {
181 | if ($this->currencies === null && $this->config('include_currency', false)) {
182 | $this->currencies = include(__DIR__ . '/Support/Currencies.php');
183 | }
184 |
185 | return Arr::get($this->currencies, $iso);
186 | }
187 |
188 | /**
189 | * Get service instance.
190 | *
191 | * @return \Torann\GeoIP\Contracts\ServiceInterface
192 | * @throws Exception
193 | */
194 | public function getService()
195 | {
196 | if ($this->service === null) {
197 | // Get service configuration
198 | $config = $this->config('services.' . $this->config('service'), []);
199 |
200 | // Get service class
201 | $class = Arr::pull($config, 'class');
202 |
203 | // Sanity check
204 | if ($class === null) {
205 | throw new Exception('No GeoIP service is configured.');
206 | }
207 |
208 | // Create service instance
209 | $this->service = new $class($config);
210 | }
211 |
212 | return $this->service;
213 | }
214 |
215 | /**
216 | * Get cache instance.
217 | *
218 | * @return \Torann\GeoIP\Cache
219 | */
220 | public function getCache()
221 | {
222 | return $this->cache;
223 | }
224 |
225 | /**
226 | * Get the client IP address.
227 | *
228 | * @return string
229 | */
230 | public function getClientIP()
231 | {
232 | $remotes_keys = [
233 | 'HTTP_X_FORWARDED_FOR',
234 | 'HTTP_CLIENT_IP',
235 | 'HTTP_X_REAL_IP',
236 | 'HTTP_X_FORWARDED',
237 | 'HTTP_FORWARDED_FOR',
238 | 'HTTP_FORWARDED',
239 | 'REMOTE_ADDR',
240 | 'HTTP_X_FORWARDED_IP',
241 | 'HTTP_X_CLUSTER_CLIENT_IP',
242 | ];
243 |
244 | foreach ($remotes_keys as $key) {
245 | if ($address = getenv($key)) {
246 | foreach (explode(',', $address) as $ip) {
247 | if ($this->isValid($ip)) {
248 | return $ip;
249 | }
250 | }
251 | }
252 | }
253 |
254 | return '127.0.0.0';
255 | }
256 |
257 | /**
258 | * Checks if the ip is valid.
259 | *
260 | * @param string $ip
261 | *
262 | * @return bool
263 | */
264 | private function isValid($ip)
265 | {
266 | if (! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
267 | && ! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE)
268 | ) {
269 | return false;
270 | }
271 |
272 | return true;
273 | }
274 |
275 | /**
276 | * Determine if the location should be cached.
277 | *
278 | * @param Location $location
279 | * @param string|null $ip
280 | *
281 | * @return bool
282 | */
283 | private function shouldCache(Location $location, $ip = null)
284 | {
285 | if ($location->default === true || $location->cached === true) {
286 | return false;
287 | }
288 |
289 | switch ($this->config('cache', 'none')) {
290 | case 'all':
291 | case 'some' && $ip === null:
292 | return true;
293 | }
294 |
295 | return false;
296 | }
297 |
298 | /**
299 | * Get configuration value.
300 | *
301 | * @param string $key
302 | * @param mixed $default
303 | *
304 | * @return mixed
305 | */
306 | public function config($key, $default = null)
307 | {
308 | return Arr::get($this->config, $key, $default);
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/src/GeoIPServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerGeoIpService();
18 |
19 | if ($this->app->runningInConsole()) {
20 | $this->registerResources();
21 | $this->registerGeoIpCommands();
22 | }
23 |
24 | if ($this->isLumen() === false) {
25 | $this->mergeConfigFrom(__DIR__ . '/../config/geoip.php', 'geoip');
26 | }
27 | }
28 |
29 | /**
30 | * Register currency provider.
31 | *
32 | * @return void
33 | */
34 | public function registerGeoIpService()
35 | {
36 | $this->app->singleton('geoip', function ($app) {
37 | return new GeoIP(
38 | $app->config->get('geoip', []),
39 | $app['cache']
40 | );
41 | });
42 | }
43 |
44 | /**
45 | * Register resources.
46 | *
47 | * @return void
48 | */
49 | public function registerResources()
50 | {
51 | if ($this->isLumen() === false) {
52 | $this->publishes([
53 | __DIR__ . '/../config/geoip.php' => config_path('geoip.php'),
54 | ], 'config');
55 | }
56 | }
57 |
58 | /**
59 | * Register commands.
60 | *
61 | * @return void
62 | */
63 | public function registerGeoIpCommands()
64 | {
65 | $this->commands([
66 | Console\Update::class,
67 | Console\Clear::class,
68 | ]);
69 | }
70 |
71 | /**
72 | * Check if package is running under Lumen app
73 | *
74 | * @return bool
75 | */
76 | protected function isLumen()
77 | {
78 | return Str::contains($this->app->version(), 'Lumen') === true;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Location.php:
--------------------------------------------------------------------------------
1 | attributes = $attributes;
46 | }
47 |
48 | /**
49 | * Determine if the location is for the same IP address.
50 | *
51 | * @param string $ip
52 | *
53 | * @return bool
54 | */
55 | public function same($ip)
56 | {
57 | return $this->getAttribute('ip') == $ip;
58 | }
59 |
60 | /**
61 | * Set a given attribute on the location.
62 | *
63 | * @param string $key
64 | * @param mixed $value
65 | *
66 | * @return $this
67 | */
68 | public function setAttribute($key, $value)
69 | {
70 | $this->attributes[$key] = $value;
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * Get an attribute from the $attributes array.
77 | *
78 | * @param string $key
79 | *
80 | * @return mixed
81 | */
82 | public function getAttribute($key)
83 | {
84 | $value = Arr::get($this->attributes, $key);
85 |
86 | $method = 'get' . Str::studly($key) . 'Attribute';
87 |
88 | // First we will check for the presence of a mutator for the set operation
89 | // which simply lets the developers tweak the attribute as it is set.
90 | if (method_exists($this, $method)) {
91 | return $this->{$method}($value);
92 | }
93 |
94 | return $value;
95 | }
96 |
97 | /**
98 | * Return the display name of the location.
99 | *
100 | * @return string
101 | */
102 | public function getDisplayNameAttribute()
103 | {
104 | return preg_replace('/^,\s/', '', "{$this->city}, {$this->state}");
105 | }
106 |
107 | /**
108 | * Is the location the default.
109 | *
110 | * @return bool
111 | */
112 | public function getDefaultAttribute($value)
113 | {
114 | return is_null($value) ? false : $value;
115 | }
116 |
117 | /**
118 | * Get the instance as an array.
119 | *
120 | * @return array
121 | */
122 | public function toArray()
123 | {
124 | return $this->attributes;
125 | }
126 |
127 | /**
128 | * Get the location's attribute
129 | *
130 | * @param string $key
131 | *
132 | * @return mixed
133 | */
134 | public function __get($key)
135 | {
136 | return $this->getAttribute($key);
137 | }
138 |
139 | /**
140 | * Set the location's attribute
141 | *
142 | * @param string $key
143 | * @param mixed $value
144 | */
145 | public function __set($key, $value)
146 | {
147 | $this->setAttribute($key, $value);
148 | }
149 |
150 | /**
151 | * Determine if the given attribute exists.
152 | *
153 | * @param mixed $offset
154 | *
155 | * @return bool
156 | */
157 | public function offsetExists(mixed $offset): bool
158 | {
159 | return isset($this->$offset);
160 | }
161 |
162 | /**
163 | * Get the value for a given offset.
164 | *
165 | * @param mixed $offset
166 | *
167 | * @return mixed
168 | */
169 | public function offsetGet(mixed $offset): mixed
170 | {
171 | return $this->$offset;
172 | }
173 |
174 | /**
175 | * Set the value for a given offset.
176 | *
177 | * @param mixed $offset
178 | * @param mixed $value
179 | *
180 | * @return void
181 | */
182 | public function offsetSet(mixed $offset, mixed $value): void
183 | {
184 | $this->$offset = $value;
185 | }
186 |
187 | /**
188 | * Unset the value for a given offset.
189 | *
190 | * @param mixed $offset
191 | *
192 | * @return void
193 | */
194 | public function offsetUnset(mixed $offset): void
195 | {
196 | unset($this->$offset);
197 | }
198 |
199 | /**
200 | * Check if the location's attribute is set
201 | *
202 | * @param $key
203 | *
204 | * @return bool
205 | */
206 | public function __isset($key)
207 | {
208 | return array_key_exists($key, $this->attributes);
209 | }
210 |
211 | /**
212 | * Unset an attribute on the location.
213 | *
214 | * @param string $key
215 | *
216 | * @return void
217 | */
218 | public function __unset($key)
219 | {
220 | unset($this->attributes[$key]);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/Services/AbstractService.php:
--------------------------------------------------------------------------------
1 | config = $config;
26 |
27 | $this->boot();
28 | }
29 |
30 | /**
31 | * The "booting" method of the service.
32 | *
33 | * @return void
34 | */
35 | public function boot()
36 | {
37 | //
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | public function hydrate(array $attributes = [])
44 | {
45 | return new Location($attributes);
46 | }
47 |
48 | /**
49 | * Get configuration value.
50 | *
51 | * @param string $key
52 | * @param mixed $default
53 | *
54 | * @return mixed
55 | */
56 | public function config($key, $default = null)
57 | {
58 | return Arr::get($this->config, $key, $default);
59 | }
60 | }
--------------------------------------------------------------------------------
/src/Services/IPApi.php:
--------------------------------------------------------------------------------
1 | 'http://ip-api.com/',
34 | 'headers' => [
35 | 'User-Agent' => 'Laravel-GeoIP',
36 | ],
37 | 'query' => [
38 | 'fields' => 49663,
39 | 'lang' => $this->config('lang', ['en']),
40 | ],
41 | ];
42 |
43 | // Using the Pro service
44 | if ($this->config('key')) {
45 | $base['base_uri'] = ($this->config('secure') ? 'https' : 'http') . '://pro.ip-api.com/';
46 | $base['query']['key'] = $this->config('key');
47 | }
48 |
49 | $this->client = new HttpClient($base);
50 |
51 | // Set continents
52 | if (file_exists($this->config('continent_path'))) {
53 | $this->continents = json_decode(file_get_contents($this->config('continent_path')), true);
54 | }
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function locate($ip)
61 | {
62 | // Get data from client
63 | $data = $this->client->get('json/' . $ip);
64 |
65 | // Verify server response
66 | if ($this->client->getErrors() !== null) {
67 | throw new Exception('Request failed (' . $this->client->getErrors() . ')');
68 | }
69 |
70 | // Parse body content
71 | $json = json_decode($data[0]);
72 |
73 | // Verify response status
74 | if ($json->status !== 'success') {
75 | throw new Exception('Request failed (' . $json->message . ')');
76 | }
77 |
78 | return $this->hydrate([
79 | 'ip' => $ip,
80 | 'iso_code' => $json->countryCode,
81 | 'country' => $json->country,
82 | 'city' => $json->city,
83 | 'state' => $json->region,
84 | 'state_name' => $json->regionName,
85 | 'postal_code' => $json->zip,
86 | 'lat' => $json->lat,
87 | 'lon' => $json->lon,
88 | 'timezone' => $json->timezone,
89 | 'continent' => $this->getContinent($json->countryCode),
90 | ]);
91 | }
92 |
93 | /**
94 | * Update function for service.
95 | *
96 | * @return string
97 | * @throws Exception
98 | */
99 | public function update()
100 | {
101 | $data = $this->client->get('https://dev.maxmind.com/static/csv/codes/country_continent.csv');
102 |
103 | // Verify server response
104 | if ($this->client->getErrors() !== null) {
105 | throw new Exception($this->client->getErrors());
106 | }
107 |
108 | $lines = explode("\n", $data[0]);
109 |
110 | array_shift($lines);
111 |
112 | $output = [];
113 |
114 | foreach ($lines as $line) {
115 | $arr = str_getcsv($line);
116 |
117 | if (count($arr) < 2) {
118 | continue;
119 | }
120 |
121 | $output[$arr[0]] = $arr[1];
122 | }
123 |
124 | // Get path
125 | $path = $this->config('continent_path');
126 |
127 | file_put_contents($path, json_encode($output));
128 |
129 | return "Continent file ({$path}) updated.";
130 | }
131 |
132 | /**
133 | * Get continent based on country code.
134 | *
135 | * @param string $code
136 | *
137 | * @return string
138 | */
139 | private function getContinent($code)
140 | {
141 | return Arr::get($this->continents, $code, 'Unknown');
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/Services/IPData.php:
--------------------------------------------------------------------------------
1 | client = new HttpClient([
31 | 'base_uri' => 'https://api.ipdata.co/',
32 | 'query' => [
33 | 'api-key' => $this->config('key'),
34 | ],
35 | ]);
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | *
41 | * @throws Exception
42 | */
43 | public function locate($ip)
44 | {
45 | // Get data from client
46 | $data = $this->client->get($ip);
47 |
48 | // Verify server response
49 | if ($this->client->getErrors() !== null || empty($data[0])) {
50 | throw new Exception('Request failed (' . $this->client->getErrors() . ')');
51 | }
52 |
53 | $json = json_decode($data[0], true);
54 |
55 | return $this->hydrate([
56 | 'ip' => $ip,
57 | 'iso_code' => $json['country_code'],
58 | 'country' => $json['country_name'],
59 | 'city' => $json['city'],
60 | 'state' => $json['region_code'],
61 | 'state_name' => $json['region'],
62 | 'postal_code' => $json['postal'],
63 | 'lat' => $json['latitude'],
64 | 'lon' => $json['longitude'],
65 | 'timezone' => Arr::get($json, 'time_zone.name'),
66 | 'continent' => Arr::get($json, 'continent_code'),
67 | 'currency' => Arr::get($json, 'currency.code'),
68 | ]);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Services/IPFinder.php:
--------------------------------------------------------------------------------
1 | client = new HttpClient([
31 | 'base_uri' => 'https://api.ipfinder.io/v1/',
32 | 'headers' => [
33 | 'User-Agent' => 'Laravel-GeoIP-Torann',
34 | ],
35 | 'query' => [
36 | 'token' => $this->config('key'),
37 | ],
38 | ]);
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | *
44 | * @throws Exception
45 | */
46 | public function locate($ip)
47 | {
48 | // Get data from client
49 | $data = $this->client->get($ip);
50 |
51 | // Verify server response
52 | if ($this->client->getErrors() !== null || empty($data[0])) {
53 | throw new Exception('Request failed (' . $this->client->getErrors() . ')');
54 | }
55 |
56 | $json = json_decode($data[0], true);
57 |
58 | return $this->hydrate($json);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Services/IPGeoLocation.php:
--------------------------------------------------------------------------------
1 | 'https://api.ipgeolocation.io/',
27 | ];
28 |
29 | if ($this->config('key')) {
30 | $base['base_uri'] = "{$base['base_uri']}ipgeo?apiKey=" . $this->config('key');
31 | }
32 |
33 | $this->client = new HttpClient($base);
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 |
40 | public function locate($ip)
41 | {
42 | // Get data from client
43 | $data = $this->client->get('&ip=' . $ip);
44 |
45 | // Verify server response
46 | if ($this->client->getErrors() !== null) {
47 | throw new Exception('Request failed (' . $this->client->getErrors() . ')');
48 | }
49 |
50 | // Parse body content
51 | $json = json_decode($data[0], true);
52 |
53 | return $this->hydrate($json);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Services/MaxMindDatabase.php:
--------------------------------------------------------------------------------
1 | config('database_path');
28 |
29 | // Copy test database for now
30 | if (is_file($path) === false) {
31 | @mkdir(dirname($path));
32 |
33 | copy(__DIR__ . '/../../resources/geoip.mmdb', $path);
34 | }
35 |
36 | $this->reader = new Reader(
37 | $path, $this->config('locales', ['en'])
38 | );
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | */
44 | public function locate($ip)
45 | {
46 | $record = $this->reader->city($ip);
47 |
48 | return $this->hydrate([
49 | 'ip' => $ip,
50 | 'iso_code' => $record->country->isoCode,
51 | 'country' => $record->country->name,
52 | 'city' => $record->city->name,
53 | 'state' => $record->mostSpecificSubdivision->isoCode,
54 | 'state_name' => $record->mostSpecificSubdivision->name,
55 | 'postal_code' => $record->postal->code,
56 | 'lat' => $record->location->latitude,
57 | 'lon' => $record->location->longitude,
58 | 'timezone' => $record->location->timeZone,
59 | 'continent' => $record->continent->code,
60 | 'localizations' => $this->getLocalizations($record),
61 | ]);
62 | }
63 |
64 | /**
65 | * Update function for service.
66 | *
67 | * @return string
68 | * @throws Exception
69 | */
70 | public function update()
71 | {
72 | if ($this->config('database_path', false) === false) {
73 | throw new Exception('Database path not set in config file.');
74 | }
75 |
76 | $this->withTemporaryDirectory(function ($directory) {
77 | $tarFile = sprintf('%s/maxmind.tar.gz', $directory);
78 |
79 | file_put_contents($tarFile, fopen($this->config('update_url'), 'r'));
80 |
81 | $archive = new PharData($tarFile);
82 |
83 | $file = $this->findDatabaseFile($archive);
84 |
85 | $relativePath = "{$archive->getFilename()}/{$file->getFilename()}";
86 |
87 | $archive->extractTo($directory, $relativePath);
88 |
89 | file_put_contents($this->config('database_path'), fopen("{$directory}/{$relativePath}", 'r'));
90 | });
91 |
92 | return "Database file ({$this->config('database_path')}) updated.";
93 | }
94 |
95 | /**
96 | * Provide a temporary directory to perform operations in and and ensure
97 | * it is removed afterwards.
98 | *
99 | * @param callable $callback
100 | *
101 | * @return void
102 | */
103 | protected function withTemporaryDirectory(callable $callback)
104 | {
105 | $directory = tempnam(sys_get_temp_dir(), 'maxmind');
106 |
107 | if (file_exists($directory)) {
108 | unlink($directory);
109 | }
110 |
111 | mkdir($directory);
112 |
113 | try {
114 | $callback($directory);
115 | } finally {
116 | $this->deleteDirectory($directory);
117 | }
118 | }
119 |
120 | /**
121 | * Recursively search the given archive to find the .mmdb file.
122 | *
123 | * @param \PharData $archive
124 | *
125 | * @return mixed
126 | * @throws \Exception
127 | */
128 | protected function findDatabaseFile($archive)
129 | {
130 | foreach ($archive as $file) {
131 | if ($file->isDir()) {
132 | return $this->findDatabaseFile(new PharData($file->getPathName()));
133 | }
134 |
135 | if (pathinfo($file, PATHINFO_EXTENSION) === 'mmdb') {
136 | return $file;
137 | }
138 | }
139 |
140 | throw new Exception('Database file could not be found within archive.');
141 | }
142 |
143 | /**
144 | * Recursively delete the given directory.
145 | *
146 | * @param string $directory
147 | *
148 | * @return mixed
149 | */
150 | protected function deleteDirectory(string $directory)
151 | {
152 | if (! file_exists($directory)) {
153 | return true;
154 | }
155 |
156 | if (! is_dir($directory)) {
157 | return unlink($directory);
158 | }
159 |
160 | foreach (scandir($directory) as $item) {
161 | if ($item == '.' || $item == '..') {
162 | continue;
163 | }
164 |
165 | if (! $this->deleteDirectory($directory . DIRECTORY_SEPARATOR . $item)) {
166 | return false;
167 | }
168 | }
169 |
170 | return rmdir($directory);
171 | }
172 |
173 | /**
174 | * Get localized country name, state name and city name based on config languages
175 | *
176 | * @param City $record
177 | * @return array
178 | */
179 | private function getLocalizations(City $record): array
180 | {
181 | $localizations = [];
182 |
183 | foreach ($this->config('locales', ['en']) as $lang) {
184 | $localizations[$lang]['country'] = Arr::get($record->country->names, $lang);
185 | $localizations[$lang]['state_name'] = Arr::get($record->mostSpecificSubdivision->names, $lang);
186 | $localizations[$lang]['city'] = Arr::get($record->city->names, $lang);
187 | }
188 |
189 | return $localizations;
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/Services/MaxMindWebService.php:
--------------------------------------------------------------------------------
1 | client = new Client(
26 | $this->config('user_id'),
27 | $this->config('license_key'),
28 | $this->config('locales', ['en'])
29 | );
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | public function locate($ip)
36 | {
37 | $record = $this->client->city($ip);
38 |
39 | return $this->hydrate([
40 | 'ip' => $ip,
41 | 'iso_code' => $record->country->isoCode,
42 | 'country' => $record->country->name,
43 | 'city' => $record->city->name,
44 | 'state' => $record->mostSpecificSubdivision->isoCode,
45 | 'state_name' => $record->mostSpecificSubdivision->name,
46 | 'postal_code' => $record->postal->code,
47 | 'lat' => $record->location->latitude,
48 | 'lon' => $record->location->longitude,
49 | 'timezone' => $record->location->timeZone,
50 | 'continent' => $record->continent->code,
51 | 'localizations' => $this->getLocalizations($record),
52 | ]);
53 | }
54 |
55 | /**
56 | * Get localized country name, state name and city name based on config languages
57 | *
58 | * @param City $record
59 | *
60 | * @return array
61 | */
62 | private function getLocalizations(City $record): array
63 | {
64 | $localizations = [];
65 |
66 | foreach ($this->config('locales', ['en']) as $lang) {
67 | $localizations[$lang]['country'] = Arr::get($record->country->names, $lang);
68 | $localizations[$lang]['state_name'] = Arr::get($record->mostSpecificSubdivision->names, $lang);
69 | $localizations[$lang]['city'] = Arr::get($record->city->names, $lang);
70 | }
71 |
72 | return $localizations;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Support/Currencies.php:
--------------------------------------------------------------------------------
1 | 'EUR',
5 | 'AE' => 'AED',
6 | 'AF' => 'AFN',
7 | 'AG' => 'XCD',
8 | 'AI' => 'XCD',
9 | 'AL' => 'ALL',
10 | 'AM' => 'AMD',
11 | 'AN' => 'ANG',
12 | 'AO' => 'AOA',
13 | 'AQ' => '',
14 | 'AR' => 'ARS',
15 | 'AS' => 'USD',
16 | 'AT' => 'EUR',
17 | 'AU' => 'AUD',
18 | 'AW' => 'AWG',
19 | 'AZ' => 'AZN',
20 | 'BA' => 'BAM',
21 | 'BB' => 'BBD',
22 | 'BD' => 'BDT',
23 | 'BE' => 'EUR',
24 | 'BF' => 'XOF',
25 | 'BG' => 'BGN',
26 | 'BH' => 'BHD',
27 | 'BI' => 'BIF',
28 | 'BJ' => 'XOF',
29 | 'BL' => 'EUR',
30 | 'BM' => 'BMD',
31 | 'BN' => 'BND',
32 | 'BO' => 'BOB',
33 | 'BR' => 'BRL',
34 | 'BS' => 'BSD',
35 | 'BT' => 'BTN',
36 | 'BV' => 'NOK',
37 | 'BW' => 'BWP',
38 | 'BY' => 'BYN',
39 | 'BZ' => 'BZD',
40 | 'CA' => 'CAD',
41 | 'CC' => 'AUD',
42 | 'CD' => 'CDF',
43 | 'CF' => 'XAF',
44 | 'CG' => 'CDF',
45 | 'CH' => 'CHF',
46 | 'CI' => 'XOF',
47 | 'CK' => 'NZD',
48 | 'CL' => 'CLP',
49 | 'CM' => 'XAF',
50 | 'CN' => 'CNY',
51 | 'CO' => 'COP',
52 | 'CR' => 'CRC',
53 | 'CU' => 'CUP',
54 | 'CV' => 'CVE',
55 | 'CW' => 'ANG',
56 | 'CX' => 'AUD',
57 | 'CY' => 'EUR',
58 | 'CZ' => 'CZK',
59 | 'DE' => 'EUR',
60 | 'DJ' => 'DJF',
61 | 'DK' => 'DKK',
62 | 'DM' => 'XCD',
63 | 'DO' => 'DOP',
64 | 'DZ' => 'DZD',
65 | 'EC' => 'USD',
66 | 'EE' => 'EUR',
67 | 'EG' => 'EGP',
68 | 'EH' => 'MAD',
69 | 'ER' => 'ERN',
70 | 'ES' => 'EUR',
71 | 'ET' => 'ETB',
72 | 'FI' => 'EUR',
73 | 'FJ' => 'FJD',
74 | 'FK' => 'FKP',
75 | 'FM' => 'USD',
76 | 'FO' => 'DKK',
77 | 'FR' => 'EUR',
78 | 'GA' => 'XAF',
79 | 'GB' => 'GBP',
80 | 'GD' => 'XCD',
81 | 'GE' => 'GEL',
82 | 'GF' => 'EUR',
83 | 'GG' => 'GGP',
84 | 'GH' => 'GHS',
85 | 'GI' => 'GIP',
86 | 'GL' => 'DKK',
87 | 'GM' => 'GMD',
88 | 'GN' => 'GNF',
89 | 'GP' => 'EUR',
90 | 'GQ' => 'XAF',
91 | 'GR' => 'EUR',
92 | 'GS' => 'GBP',
93 | 'GT' => 'GTQ',
94 | 'GU' => 'USD',
95 | 'GW' => 'XOF',
96 | 'GY' => 'GYD',
97 | 'HK' => 'HKD',
98 | 'HM' => 'AUD',
99 | 'HN' => 'HNL',
100 | 'HR' => 'HRK',
101 | 'HT' => 'HTG',
102 | 'HU' => 'HUF',
103 | 'ID' => 'IDR',
104 | 'IE' => 'EUR',
105 | 'IL' => 'ILS',
106 | 'IM' => 'IMP',
107 | 'IN' => 'INR',
108 | 'IO' => 'USD',
109 | 'IQ' => 'IQD',
110 | 'IR' => 'IRR',
111 | 'IS' => 'ISK',
112 | 'IT' => 'EUR',
113 | 'JE' => 'JEP',
114 | 'JM' => 'JMD',
115 | 'JO' => 'JOD',
116 | 'JP' => 'JPY',
117 | 'KE' => 'KES',
118 | 'KG' => 'KGS',
119 | 'KH' => 'KHR',
120 | 'KI' => 'AUD',
121 | 'KM' => 'KMF',
122 | 'KN' => 'XCD',
123 | 'KP' => 'KPW',
124 | 'KR' => 'KRW',
125 | 'KW' => 'KWD',
126 | 'KY' => 'KYD',
127 | 'KZ' => 'KZT',
128 | 'LA' => 'LAK',
129 | 'LB' => 'LBP',
130 | 'LC' => 'XCD',
131 | 'LI' => 'CHF',
132 | 'LK' => 'LKR',
133 | 'LR' => 'LRD',
134 | 'LS' => 'LSL',
135 | 'LT' => 'EUR',
136 | 'LU' => 'EUR',
137 | 'LV' => 'EUR',
138 | 'LY' => 'LYD',
139 | 'MA' => 'MAD',
140 | 'MC' => 'EUR',
141 | 'MD' => 'MDL',
142 | 'ME' => 'EUR',
143 | 'MG' => 'MGA',
144 | 'MH' => 'USD',
145 | 'MK' => 'MKD',
146 | 'ML' => 'XOF',
147 | 'MM' => 'MMK',
148 | 'MN' => 'MNT',
149 | 'MO' => 'MOP',
150 | 'MP' => 'USD',
151 | 'MQ' => 'EUR',
152 | 'MR' => 'MRO',
153 | 'MS' => 'XCD',
154 | 'MT' => 'EUR',
155 | 'MU' => 'MUR',
156 | 'MV' => 'MVR',
157 | 'MW' => 'MWK',
158 | 'MX' => 'MXN',
159 | 'MY' => 'MYR',
160 | 'MZ' => 'MZN',
161 | 'NA' => 'NAD',
162 | 'NC' => 'XPF',
163 | 'NE' => 'XOF',
164 | 'NF' => 'AUD',
165 | 'NG' => 'NGN',
166 | 'NI' => 'NIO',
167 | 'NL' => 'EUR',
168 | 'NO' => 'NOK',
169 | 'NP' => 'NPR',
170 | 'NR' => 'AUD',
171 | 'NU' => 'NZD',
172 | 'NZ' => 'NZD',
173 | 'OM' => 'OMR',
174 | 'PA' => 'PAB',
175 | 'PE' => 'PEN',
176 | 'PF' => 'XPF',
177 | 'PG' => 'PGK',
178 | 'PH' => 'PHP',
179 | 'PK' => 'PKR',
180 | 'PL' => 'PLN',
181 | 'PM' => 'EUR',
182 | 'PN' => 'GBP',
183 | 'PR' => 'USD',
184 | 'PS' => 'ILS',
185 | 'PT' => 'EUR',
186 | 'PW' => 'USD',
187 | 'PY' => 'PYG',
188 | 'QA' => 'QAR',
189 | 'RE' => 'EUR',
190 | 'RO' => 'RON',
191 | 'RS' => 'RSD',
192 | 'RU' => 'RUB',
193 | 'RW' => 'RWF',
194 | 'SA' => 'SAR',
195 | 'SB' => 'SBD',
196 | 'SC' => 'SCR',
197 | 'SD' => 'SDG',
198 | 'SE' => 'SEK',
199 | 'SG' => 'SGD',
200 | 'SH' => 'SHP',
201 | 'SI' => 'EUR',
202 | 'SJ' => 'NOK',
203 | 'SK' => 'EUR',
204 | 'SL' => 'SLL',
205 | 'SM' => 'EUR',
206 | 'SN' => 'XOF',
207 | 'SO' => 'SOS',
208 | 'SR' => 'SRD',
209 | 'SS' => 'SSP',
210 | 'ST' => 'STD',
211 | 'SV' => 'USD',
212 | 'SX' => 'ANG',
213 | 'SY' => 'SYP',
214 | 'SZ' => 'SZL',
215 | 'TC' => 'USD',
216 | 'TD' => 'XAF',
217 | 'TF' => 'EUR',
218 | 'TG' => 'XOF',
219 | 'TH' => 'THB',
220 | 'TJ' => 'TJS',
221 | 'TK' => 'NZD',
222 | 'TL' => 'USD',
223 | 'TM' => 'TMT',
224 | 'TN' => 'TND',
225 | 'TO' => 'TOP',
226 | 'TR' => 'TRY',
227 | 'TT' => 'TTD',
228 | 'TV' => 'AUD',
229 | 'TW' => 'TWD',
230 | 'TZ' => 'TZS',
231 | 'UA' => 'UAH',
232 | 'UG' => 'UGX',
233 | 'US' => 'USD',
234 | 'UY' => 'UYU',
235 | 'UZ' => 'UZS',
236 | 'VA' => 'EUR',
237 | 'VC' => 'XCD',
238 | 'VE' => 'VEF',
239 | 'VG' => 'USD',
240 | 'VI' => 'USD',
241 | 'VN' => 'VND',
242 | 'VU' => 'VUV',
243 | 'WF' => 'XPF',
244 | 'WS' => 'WST',
245 | 'XK' => 'EUR',
246 | 'YE' => 'YER',
247 | 'YT' => 'EUR',
248 | 'ZA' => 'ZAR',
249 | 'ZM' => 'ZMK',
250 | 'ZW' => 'ZWL',
251 | ];
252 |
--------------------------------------------------------------------------------
/src/Support/HttpClient.php:
--------------------------------------------------------------------------------
1 | '',
16 | 'headers' => [],
17 | 'query' => [],
18 | ];
19 |
20 | /**
21 | * Last request http status.
22 | *
23 | * @var int
24 | **/
25 | protected $http_code = 200;
26 |
27 | /**
28 | * Last request error string.
29 | *
30 | * @var string
31 | **/
32 | protected $errors = null;
33 |
34 | /**
35 | * Array containing headers from last performed request.
36 | *
37 | * @var array
38 | */
39 | private $headers = [];
40 |
41 | /**
42 | * HttpClient constructor.
43 | *
44 | * @param array $config
45 | */
46 | public function __construct(array $config = [])
47 | {
48 | $this->config = $config;
49 | }
50 |
51 | /**
52 | * Perform a get request.
53 | *
54 | * @param string $url
55 | * @param array $query
56 | * @param array $headers
57 | *
58 | * @return array
59 | */
60 | public function get($url, array $query = [], array $headers = [])
61 | {
62 | return $this->execute('GET', $this->buildGetUrl($url, $query), [], $headers);
63 | }
64 |
65 | /**
66 | * Execute the curl request
67 | *
68 | * @param string $method
69 | * @param string $url
70 | * @param array $query
71 | * @param array $headers
72 | *
73 | * @return array
74 | */
75 | public function execute($method, $url, array $query = [], array $headers = [])
76 | {
77 | // Merge global and request headers
78 | $headers = array_merge(
79 | Arr::get($this->config, 'headers', []),
80 | $headers
81 | );
82 |
83 | // Merge global and request queries
84 | $query = array_merge(
85 | Arr::get($this->config, 'query', []),
86 | $query
87 | );
88 |
89 | $this->errors = null;
90 |
91 | $curl = curl_init();
92 |
93 | // Set options
94 | curl_setopt_array($curl, [
95 | CURLOPT_URL => $this->getUrl($url),
96 | CURLOPT_HTTPHEADER => $headers,
97 | CURLOPT_CONNECTTIMEOUT => 20,
98 | CURLOPT_TIMEOUT => 90,
99 | CURLOPT_RETURNTRANSFER => 1,
100 | CURLOPT_SSL_VERIFYPEER => 0,
101 | CURLOPT_SSL_VERIFYHOST => 0,
102 | CURLOPT_HEADER => 1,
103 | CURLINFO_HEADER_OUT => 1,
104 | CURLOPT_VERBOSE => 1,
105 | ]);
106 |
107 | // Setup method specific options
108 | switch ($method) {
109 | case 'PUT':
110 | case 'PATCH':
111 | case 'POST':
112 | curl_setopt_array($curl, [
113 | CURLOPT_CUSTOMREQUEST => $method,
114 | CURLOPT_POST => true,
115 | CURLOPT_POSTFIELDS => $query,
116 | ]);
117 | break;
118 |
119 | case 'DELETE':
120 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
121 | break;
122 |
123 | default:
124 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
125 | break;
126 | }
127 |
128 | // Make request
129 | curl_setopt($curl, CURLOPT_HEADER, true);
130 | $response = curl_exec($curl);
131 |
132 | // Set HTTP response code
133 | $this->http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
134 |
135 | // Set errors if there are any
136 | if (curl_errno($curl)) {
137 | $this->errors = curl_error($curl);
138 | }
139 |
140 | // Parse body
141 | $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
142 | $header = substr($response, 0, $header_size);
143 | $body = substr($response, $header_size);
144 |
145 | curl_close($curl);
146 |
147 | return [$body, $this->parseHeaders($header)];
148 | }
149 |
150 | /**
151 | * Check if the curl request ended up with errors
152 | *
153 | * @return bool
154 | */
155 | public function hasErrors()
156 | {
157 | return is_null($this->errors) === false;
158 | }
159 |
160 | /**
161 | * Get curl errors
162 | *
163 | * @return string
164 | */
165 | public function getErrors()
166 | {
167 | return $this->errors;
168 | }
169 |
170 | /**
171 | * Get last curl HTTP code.
172 | *
173 | * @return int
174 | */
175 | public function getHttpCode()
176 | {
177 | return $this->http_code;
178 | }
179 |
180 | /**
181 | * Parse string headers into array
182 | *
183 | * @param string $headers
184 | *
185 | * @return array
186 | */
187 | private function parseHeaders($headers)
188 | {
189 | $result = [];
190 |
191 | foreach (preg_split("/\\r\\n|\\r|\\n/", $headers) as $row) {
192 | $header = explode(':', $row, 2);
193 |
194 | if (count($header) == 2) {
195 | $result[$header[0]] = trim($header[1]);
196 | }
197 | else {
198 | $result[] = $header[0];
199 | }
200 | }
201 |
202 | return $result;
203 | }
204 |
205 | /**
206 | * Get request URL.
207 | *
208 | * @param string $url
209 | *
210 | * @return string
211 | */
212 | private function getUrl($url)
213 | {
214 | // Check for URL scheme
215 | if (parse_url($url, PHP_URL_SCHEME) === null) {
216 | $url = Arr::get($this->config, 'base_uri') . $url;
217 | }
218 |
219 | return $url;
220 | }
221 |
222 | /**
223 | * Build a GET request string.
224 | *
225 | * @param string $url
226 | * @param array $query
227 | *
228 | * @return string
229 | */
230 | private function buildGetUrl($url, array $query = [])
231 | {
232 | // Merge global and request queries
233 | $query = array_merge(
234 | Arr::get($this->config, 'query', []),
235 | $query
236 | );
237 |
238 | // Append query
239 | if ($query = http_build_query($query)) {
240 | $url .= strpos($url, '?') ? $query : "?{$query}";
241 | }
242 |
243 | return $url;
244 | }
245 | }
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 | getLocation($ip);
18 | }
19 | }
--------------------------------------------------------------------------------