Code

< 40 %
40-60 %
> 60 %
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Geocoder package.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT License
11
 */
12
13
namespace Geocoder\Provider\IpInfoDb;
14
15
use Geocoder\Collection;
16
use Geocoder\Exception\InvalidArgument;
17
use Geocoder\Exception\InvalidCredentials;
18
use Geocoder\Exception\UnsupportedOperation;
19
use Geocoder\Http\Provider\AbstractHttpProvider;
20
use Geocoder\Model\Address;
21
use Geocoder\Model\AddressCollection;
22
use Geocoder\Provider\Provider;
23
use Geocoder\Query\GeocodeQuery;
24
use Geocoder\Query\ReverseQuery;
25
use Psr\Http\Client\ClientInterface;
26
27
/**
28
 * @author William Durand <[email protected]>
29
 */
30
final class IpInfoDb extends AbstractHttpProvider implements Provider
31
{
32
    /**
33
     * @var string
34
     */
35
    public const CITY_PRECISION_ENDPOINT_URL = 'https://api.ipinfodb.com/v3/ip-city/?key=%s&format=json&ip=%s';
36
37
    /**
38
     * @var string
39
     */
40
    public const COUNTRY_PRECISION_ENDPOINT_URL = 'https://api.ipinfodb.com/v3/ip-country/?key=%s&format=json&ip=%s';
41
42
    /**
43
     * @var string
44
     */
45
    private $apiKey;
46
47
    /**
48
     * @var string
49
     */
50
    private $endpointUrl;
51
52
    /**
53
     * @param ClientInterface $client    an HTTP adapter
54
     * @param string          $apiKey    an API key
55
     * @param string          $precision The endpoint precision. Either "city" or "country" (faster)
56
     *
57
     * @throws \Geocoder\Exception\InvalidArgument
58
     */
59 18
    public function __construct(ClientInterface $client, string $apiKey, string $precision = 'city')
60
    {
61 18
        parent::__construct($client);
62
63 18
        $this->apiKey = $apiKey;
64
        switch ($precision) {
65 18
            case 'city':
66 16
                $this->endpointUrl = self::CITY_PRECISION_ENDPOINT_URL;
67
68 16
                break;
69
70 2
            case 'country':
71 1
                $this->endpointUrl = self::COUNTRY_PRECISION_ENDPOINT_URL;
72
73 1
                break;
74
75
            default:
76 1
                throw new InvalidArgument(sprintf('Invalid precision value "%s" (allowed values: "city", "country").', $precision));
77
        }
78
    }
79
80 15
    public function geocodeQuery(GeocodeQuery $query): Collection
81
    {
82 15
        $address = $query->getText();
83 15
        if (null === $this->apiKey) {
84
            throw new InvalidCredentials('No API Key provided.');
85
        }
86
87 15
        if (!filter_var($address, FILTER_VALIDATE_IP)) {
88 2
            throw new UnsupportedOperation('The IpInfoDb provider does not support street addresses, only IPv4 addresses.');
89
        }
90
91
        // This API does not support IPv6
92 13
        if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
93 2
            throw new UnsupportedOperation('The IpInfoDb provider does not support IPv6 addresses, only IPv4 addresses.');
94
        }
95
96 11
        if ('127.0.0.1' === $address) {
97 1
            return new AddressCollection([$this->getLocationForLocalhost()]);
98
        }
99
100 10
        $url = sprintf($this->endpointUrl, $this->apiKey, $address);
101
102 10
        return $this->executeQuery($url);
103
    }
104
105 1
    public function reverseQuery(ReverseQuery $query): Collection
106
    {
107 1
        throw new UnsupportedOperation('The IpInfoDb provider is not able to do reverse geocoding.');
108
    }
109
110 5
    public function getName(): string
111
    {
112 5
        return 'ip_info_db';
113
    }
114
115 10
    private function executeQuery(string $url): AddressCollection
116
    {
117 10
        $content = $this->getUrlContents($url);
118 3
        $data = json_decode($content, true);
119
120 3
        if (empty($data) || 'OK' !== $data['statusCode']) {
121
            return new AddressCollection([]);
122
        }
123
124 3
        $timezone = null;
125 3
        if (isset($data['timeZone'])) {
126 2
            $timezone = timezone_name_from_abbr('', (int) substr($data['timeZone'], 0, strpos($data['timeZone'], ':')) * 3600, 0);
127
        }
128
129 3
        return new AddressCollection([
130 3
            Address::createFromArray([
131 3
                'providedBy' => $this->getName(),
132 3
                'latitude' => $data['latitude'] ?? null,
133 3
                'longitude' => $data['longitude'] ?? null,
134 3
                'locality' => $data['cityName'] ?? null,
135 3
                'postalCode' => $data['zipCode'] ?? null,
136 3
                'adminLevels' => isset($data['regionName']) ? [['name' => $data['regionName'], 'level' => 1]] : [],
137 3
                'country' => $data['countryName'] ?? null,
138 3
                'countryCode' => $data['countryCode'] ?? null,
139 3
                'timezone' => $timezone,
140 3
            ]),
141 3
        ]);
142
    }
143
}
144