diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..99bafec --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,38 @@ +name: PHP Tests + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up PHP ${{ matrix.php-version }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: bcmath, curl, json + coverage: none + + - name: Install Composer dependencies + run: | + if [ "${{ matrix.php-version }}" = "7.2" ]; then + composer require --dev phpunit/phpunit:^8.0 --no-update + fi + composer install --no-progress --prefer-dist + + - name: Run tests + run: vendor/bin/phpunit diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..7728509 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,32 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + # You can also specify other tool versions: + # nodejs: "19" + # rust: "1.64" + # golang: "1.19" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/source/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt \ No newline at end of file diff --git a/LICENSE.TXT b/LICENSE.TXT index 3f56aae..5391874 100644 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 IP2Location.com +Copyright (c) 2023 - 2025 IP2Location.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 53ae89a..9888b2e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ IP2Location (PHP Module) *This is the official release maintained by IP2Location.com* -This PHP module provides fast lookup of country, region, city, latitude, longitude, ZIP code, time zone, ISP, domain name, connection speed, IDD code, area code, weather station code, weather station name, MNC, MCC, mobile brand, elevation, usage type, address type and IAB category from IP address by using IP2Location database. This module uses a file based database available at IP2Location.com. +This PHP module provides fast lookup of country, region, district, city, latitude, longitude, ZIP code, time zone, ISP, domain name, connection speed, IDD code, area code, weather station code, weather station name, MNC, MCC, mobile brand, elevation, usage type, address type, IAB category and ASN from IP address by using IP2Location database. This module uses a file based database available at IP2Location.com. This module can be used in many types of projects such as: @@ -29,81 +29,9 @@ Monthly update is available for both IP2Location LITE and commercial database. 2. **Extensible.** If you require different granularity of IP information, you can visit [IP2Location.com](https://www.ip2location.com/databases) to download the relevant BIN file, and the information will made ready for you. 3. **Comprehensive Information.** There are more than 13 types of information that you can retrieve from an IP address. Please visit [IP2Location.com](https://www.ip2location.com/databases) for details. -## INSTALLATION - -Install this package using **composer** as below: - -``` -composer require ip2location/ip2location-php -``` - -To test this installation, please browse examples/example.php using web browser. - -## USAGE - -You can check the **example.php** file to learn more about usage. - -### Database Class -Below is the description of the functions available in the **Database** class. - -| Function Name | Description | -|---|---| -|Constructor|Expect 2 input parameters:
'; +echo 'IP Number : ' . $records['ipNumber'] . "\n"; +echo 'IP Version : ' . $records['ipVersion'] . "\n"; +echo 'IP Address : ' . $records['ipAddress'] . "\n"; +echo 'Country Code : ' . $records['countryCode'] . "\n"; +echo 'Country Name : ' . $records['countryName'] . "\n"; +echo 'Region Name : ' . $records['regionName'] . "\n"; +echo 'City Name : ' . $records['cityName'] . "\n"; +echo 'Latitude : ' . $records['latitude'] . "\n"; +echo 'Longitude : ' . $records['longitude'] . "\n"; +echo 'Area Code : ' . $records['areaCode'] . "\n"; +echo 'IDD Code : ' . $records['iddCode'] . "\n"; +echo 'Weather Station Code : ' . $records['weatherStationCode'] . "\n"; +echo 'Weather Station Name : ' . $records['weatherStationName'] . "\n"; +echo 'MCC : ' . $records['mcc'] . "\n"; +echo 'MNC : ' . $records['mnc'] . "\n"; +echo 'Mobile Carrier : ' . $records['mobileCarrierName'] . "\n"; +echo 'Usage Type : ' . $records['usageType'] . "\n"; +echo 'Elevation : ' . $records['elevation'] . "\n"; +echo 'Net Speed : ' . $records['netSpeed'] . "\n"; +echo 'Time Zone : ' . $records['timeZone'] . "\n"; +echo 'ZIP Code : ' . $records['zipCode'] . "\n"; +echo 'Domain Name : ' . $records['domainName'] . "\n"; +echo 'ISP Name : ' . $records['isp'] . "\n"; +echo 'Address Type : ' . $records['addressType'] . "\n"; +echo 'Category : ' . $records['category'] . "\n"; +echo 'District : ' . $records['district'] . "\n"; +echo 'ASN : ' . $records['asn'] . "\n"; +echo 'AS : ' . $records['as'] . "\n"; +echo ''; +?> +``` + +### Processing IP address using IP Tools class + +You can manupulate IP address, IP number and CIDR as below: + +``` php +isIpv4('8.8.8.8')); + +echo '
'; +print_r($ipTools->ipv4ToCidr('8.0.0.0', '8.255.255.255')); +echo ''; + +// Convert CIDR to IPv4 range +echo '
'; +print_r($ipTools->cidrToIpv4('8.0.0.0/8')); +echo ''; + +// Convert CIDR into IPv6 range +echo '
'; +print_r($ipTools->cidrToIpv6('2002::1234:abcd:ffff:c0a8:101/64')); +echo ''; + +// Convert IPv6 range into CIDR +echo '
'; +print_r($ipTools->ipv6ToCidr('2002:0000:0000:1234:abcd:ffff:c0a8:0000', '2002:0000:0000:1234:ffff:ffff:ffff:ffff')); +echo ''; + +// Compress IPv6 +echo '
'; +print_r($ipTools->compressIpv6('2002:0000:0000:1234:FFFF:FFFF:FFFF:FFFF')); +echo ''; + +// Expand IPv6 +echo '
'; +print_r($ipTools->expandIpv6('2002::1234:FFFF:FFFF:FFFF:FFFF')); +echo ''; + +echo '
diff --git a/ip2location_bin_download.php b/ip2location_bin_download.php index 5947524..1d4bf2e 100644 --- a/ip2location_bin_download.php +++ b/ip2location_bin_download.php @@ -15,12 +15,13 @@ define('DBTEMP', ROOT . 'dbtemp' . DS); -$shortOpt = ""; +$shortOpt = "y"; $longOpt = ["token:", "file:"]; $inputs = getopt($shortOpt, $longOpt); $token = ''; $dbCode = ''; +$passReplace = false; $fileName = ''; $fileNameDwld = ''; $fileSize = 0; @@ -33,9 +34,35 @@ $dbCode = $inputs["file"]; } +if(isset($inputs["y"])) { + $passReplace = true; +} + +$envFilePath = realpath(ROOT . ".env"); +$varArr = []; +if (is_file($envFilePath)) { + if (is_readable($envFilePath)) { + $fopen = fopen($envFilePath, 'r'); + if ($fopen){ + while (($line = fgets($fopen)) !== false) { + $commentLine = (substr(trim($line),0 , 1) == '#') ? true: false; + if ($commentLine || empty(trim($line))) { + continue; + } + $varLine = explode("#", $line, 2)[0]; + $envEx = preg_split('/(\s?)\=(\s?)/', $varLine); + $envName = trim($envEx[0]); + $envValue = isset($envEx[1]) ? trim($envEx[1]) : ""; + $varArr[$envName] = $envValue; + } + fclose($fopen); + } + } +} + if ($token == '') { - if (!empty(getenv('DOWNLOAD_TOKEN'))) { - $token = getenv('DOWNLOAD_TOKEN'); + if (isset($varArr['DOWNLOAD_TOKEN'])) { + $token = $varArr['DOWNLOAD_TOKEN']; } else { echo "[Error] Missing --token command line switch or parameter.\n"; exit; @@ -43,8 +70,8 @@ } if ($dbCode == '') { - if (!empty(getenv('DATABASE_CODE'))) { - $dbCode = getenv('DATABASE_CODE'); + if (isset($varArr['DATABASE_CODE'])) { + $dbCode = $varArr['DATABASE_CODE']; } else { echo "[Error] Missing --file command line switch or parameter.\n"; exit; @@ -357,7 +384,11 @@ } if ($fileName != '') { - $action = readline('The ' . substr($fileName, 0, -4) . ' file inside the data folder will be replaced. Would you like to proceed? (y/n): '); + if ($passReplace) { + $action = 'y'; + } else { + $action = readline('The ' . substr($fileName, 0, -4) . ' file inside the data folder will be replaced. Would you like to proceed? (y/n): '); + } } else { echo "[Error] Unknown --file command line parameter.\n"; exit; @@ -415,7 +446,7 @@ $zip = new ZipArchive; $res = $zip->open($fileName); if ($res === TRUE) { - $zip->extractTo("dbtemp"); + $zip->extractTo(DBTEMP); } else { echo "[Error] Unzip error of " . $fileName . ".\n"; exit; @@ -432,7 +463,7 @@ rmdir(DBTEMP); unlink($fileName); - echo "[Success] The " . substr($fileName, 0, -4) . " file has been successfully downloaded into data folder.\n"; + echo "[Success] The " . substr($fileName, 0, -4) . " file has been successfully downloaded into the data folder.\n"; } else { exit; } diff --git a/src/Database.php b/src/Database.php index 69c780d..7858a74 100644 --- a/src/Database.php +++ b/src/Database.php @@ -12,7 +12,7 @@ class Database * * @var string */ - public const VERSION = '9.6.0'; + public const VERSION = '9.7.3'; /** * Unsupported field message. @@ -203,6 +203,27 @@ class Database */ public const CATEGORY = 22; + /** + * District. + * + * @var int + */ + public const DISTRICT = 23; + + /** + * ASN. + * + * @var int + */ + public const ASN = 24; + + /** + * AS. + * + * @var int + */ + public const AS = 25; + /** * Country name and code. * @@ -394,28 +415,31 @@ class Database * @var array */ private $columns = [ - self::COUNTRY_CODE => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], - self::COUNTRY_NAME => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], - self::REGION_NAME => [0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - self::CITY_NAME => [0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], - self::LATITUDE => [0, 0, 0, 0, 20, 20, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20], - self::LONGITUDE => [0, 0, 0, 0, 24, 24, 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24], - self::ZIP_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 28, 28, 0, 28, 28, 28, 0, 28, 0, 28, 28, 28, 0, 28, 28], - self::TIME_ZONE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 32, 28, 32, 32, 32, 28, 32, 0, 32, 32, 32, 0, 32, 32], - self::ISP => [0, 12, 0, 20, 0, 28, 20, 28, 0, 32, 0, 36, 0, 36, 0, 36, 0, 36, 28, 36, 0, 36, 28, 36, 36], - self::DOMAIN_NAME => [0, 0, 0, 0, 0, 0, 24, 32, 0, 36, 0, 40, 0, 40, 0, 40, 0, 40, 32, 40, 0, 40, 32, 40, 40], - self::NET_SPEED => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 44, 0, 44, 32, 44, 0, 44, 0, 44, 0, 44, 44], - self::IDD_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 48, 0, 48, 0, 48, 36, 48, 0, 48, 48], - self::AREA_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 52, 0, 52, 0, 52, 40, 52, 0, 52, 52], - self::WEATHER_STATION_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 56, 0, 56, 0, 56, 0, 56, 56], - self::WEATHER_STATION_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 60, 0, 60, 0, 60, 0, 60, 60], - self::MCC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 64, 36, 64, 64], - self::MNC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 68, 0, 68, 40, 68, 68], - self::MOBILE_CARRIER_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 72, 0, 72, 44, 72, 72], - self::ELEVATION => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 76, 0, 76, 76], - self::USAGE_TYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 80, 80], - self::ADDRESS_TYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84], - self::CATEGORY => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88], + self::COUNTRY_CODE => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + self::COUNTRY_NAME => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + self::REGION_NAME => [0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + self::CITY_NAME => [0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], + self::LATITUDE => [0, 0, 0, 0, 20, 20, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20], + self::LONGITUDE => [0, 0, 0, 0, 24, 24, 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24], + self::ZIP_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 28, 28, 0, 28, 28, 28, 0, 28, 0, 28, 28, 28, 0, 28, 28, 28], + self::TIME_ZONE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 32, 28, 32, 32, 32, 28, 32, 0, 32, 32, 32, 0, 32, 32, 32], + self::ISP => [0, 12, 0, 20, 0, 28, 20, 28, 0, 32, 0, 36, 0, 36, 0, 36, 0, 36, 28, 36, 0, 36, 28, 36, 36, 36], + self::DOMAIN_NAME => [0, 0, 0, 0, 0, 0, 24, 32, 0, 36, 0, 40, 0, 40, 0, 40, 0, 40, 32, 40, 0, 40, 32, 40, 40, 40], + self::NET_SPEED => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 44, 0, 44, 32, 44, 0, 44, 0, 44, 0, 44, 44, 44], + self::IDD_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 48, 0, 48, 0, 48, 36, 48, 0, 48, 48, 48], + self::AREA_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 52, 0, 52, 0, 52, 40, 52, 0, 52, 52, 52], + self::WEATHER_STATION_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 56, 0, 56, 0, 56, 0, 56, 56, 56], + self::WEATHER_STATION_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 60, 0, 60, 0, 60, 0, 60, 60, 60], + self::MCC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 64, 36, 64, 64, 64], + self::MNC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 68, 0, 68, 40, 68, 68, 68], + self::MOBILE_CARRIER_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 72, 0, 72, 44, 72, 72, 72], + self::ELEVATION => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 76, 0, 76, 76, 76], + self::USAGE_TYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 80, 80, 80], + self::ADDRESS_TYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 84], + self::CATEGORY => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88, 88], + self::DISTRICT => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92], + self::ASN => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96], + self::AS => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100], ]; /** @@ -446,6 +470,9 @@ class Database self::USAGE_TYPE => 'usageType', self::ADDRESS_TYPE => 'addressType', self::CATEGORY => 'category', + self::DISTRICT => 'district', + self::ASN => 'asn', + self::AS => 'as', self::IP_ADDRESS => 'ipAddress', self::IP_VERSION => 'ipVersion', self::IP_NUMBER => 'ipNumber', @@ -458,6 +485,7 @@ class Database */ private $databases = [ // IPv4 databases + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY-DISTRICT-ASN', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE', 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE-USAGETYPE', @@ -485,6 +513,7 @@ class Database 'IP-COUNTRY', // IPv6 databases + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY-DISTRICT-ASN', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE-ADDRESSTYPE-CATEGORY', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE', 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE-USAGETYPE', @@ -968,6 +997,10 @@ public function lookup($ip, $fields = null, $asNamed = true) // Get IP version and number list($ipVersion, $ipNumber) = $this->ipVersionAndNumber($ip); + if (!$ipVersion) { + return false; + } + // Perform a binary search $pointer = $this->binSearch($ipVersion, $ipNumber); @@ -1003,6 +1036,9 @@ public function lookup($ip, $fields = null, $asNamed = true) $ifields[] = self::USAGE_TYPE; $ifields[] = self::ADDRESS_TYPE; $ifields[] = self::CATEGORY; + $ifields[] = self::DISTRICT; + $ifields[] = self::ASN; + $ifields[] = self::AS; $ifields[] = self::COUNTRY; $ifields[] = self::COORDINATES; @@ -1046,6 +1082,9 @@ public function lookup($ip, $fields = null, $asNamed = true) self::USAGE_TYPE => false, self::ADDRESS_TYPE => false, self::CATEGORY => false, + self::DISTRICT => false, + self::ASN => false, + self::AS => false, self::COUNTRY => false, self::COORDINATES => false, self::IDD_AREA => false, @@ -1264,6 +1303,27 @@ public function lookup($ip, $fields = null, $asNamed = true) } break; + case self::DISTRICT: + if (!$done[self::DISTRICT]) { + $results[self::DISTRICT] = $this->readDistrict($pointer); + $done[self::DISTRICT] = true; + } + break; + + case self::ASN: + if (!$done[self::ASN]) { + $results[self::ASN] = $this->readAsn($pointer); + $done[self::ASN] = true; + } + break; + + case self::AS: + if (!$done[self::AS]) { + $results[self::AS] = $this->readAs($pointer); + $done[self::AS] = true; + } + break; + case self::IP_ADDRESS: if (!$done[self::IP_ADDRESS]) { $results[self::IP_ADDRESS] = $ip; @@ -2037,6 +2097,66 @@ private function readCategory($pointer) return $this->readString($this->columns[self::CATEGORY][$this->type]); } + /** + * High level function to fetch the district. + * + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * + * @return string + */ + private function readDistrict($pointer) + { + if ($pointer === false) { + return self::INVALID_IP_ADDRESS; + } + + if ($this->columns[self::DISTRICT][$this->type] === 0) { + return self::FIELD_NOT_SUPPORTED; + } + + return $this->readString($this->columns[self::DISTRICT][$this->type]); + } + + /** + * High level function to fetch the ASN. + * + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * + * @return string + */ + private function readAsn($pointer) + { + if ($pointer === false) { + return self::INVALID_IP_ADDRESS; + } + + if ($this->columns[self::ASN][$this->type] === 0) { + return self::FIELD_NOT_SUPPORTED; + } + + return $this->readString($this->columns[self::ASN][$this->type]); + } + + /** + * High level function to fetch the AS. + * + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * + * @return string + */ + private function readAs($pointer) + { + if ($pointer === false) { + return self::INVALID_IP_ADDRESS; + } + + if ($this->columns[self::AS][$this->type] === 0) { + return self::FIELD_NOT_SUPPORTED; + } + + return $this->readString($this->columns[self::AS][$this->type]); + } + /** * Get the boundaries for an IP address. * diff --git a/src/IpTools.php b/src/IpTools.php index e85a433..a00c6f4 100644 --- a/src/IpTools.php +++ b/src/IpTools.php @@ -194,37 +194,25 @@ public function cidrToIpv6($cidr) list($ip, $range) = explode('/', $cidr); - // Convert the IPv6 into binary - $binFirstAddress = inet_pton($ip); + $parts = explode(':', $this->expandIpv6($ip)); - // Convert the binary string to a string with hexadecimal characters - $hexStartAddress = @reset(@unpack('H*0', $binFirstAddress)); + $bitStart = str_repeat('1', $range) . str_repeat('0', 128 - $range); + $bitEnd = str_repeat('0', $range) . str_repeat('1', 128 - $range); - // Get available bits - $bits = 128 - $range; + $floors = str_split($bitStart, 16); + $ceilings = str_split($bitEnd, 16); - $hexLastAddress = $hexStartAddress; + $start = []; + $end = []; - $pos = 31; - while ($bits > 0) { - // Convert current character to an integer - $int = hexdec(substr($hexLastAddress, $pos, 1)); - - // Convert it back to a hexadecimal character - $new = dechex($int | (pow(2, min(4, $bits)) - 1)); - - // And put that character back in the string - $hexLastAddress = substr_replace($hexLastAddress, $new, $pos, 1); - - $bits -= 4; - --$pos; + for ($i = 0; $i < 8; ++$i) { + $start[] = dechex(hexdec($parts[$i]) & hexdec(base_convert($floors[$i], 2, 16))) . ':'; + $end[] = dechex(hexdec($parts[$i]) | hexdec(base_convert($ceilings[$i], 2, 16))) . ':'; } - $binLastAddress = pack('H*', $hexLastAddress); - return [ - 'ip_start' => $this->expand(inet_ntop($binFirstAddress)), - 'ip_end' => $this->expand(inet_ntop($binLastAddress)), + 'ip_start' => $this->expandIpv6(substr(implode('', $start), 0, -1)), + 'ip_end' => $this->expandIpv6(substr(implode('', $end), 0, -1)), ]; } diff --git a/src/WebService.php b/src/WebService.php index 21c8944..bd31bac 100644 --- a/src/WebService.php +++ b/src/WebService.php @@ -27,6 +27,10 @@ class WebService * @var int */ public const EXCEPTION_WEB_SERVICE_ERROR = 10003; + + private $apiKey; + private $package; + private $useSsl; /** * Constructor. diff --git a/tests/DatabaseTest.php b/tests/DatabaseTest.php index d3e3330..d3c52e4 100644 --- a/tests/DatabaseTest.php +++ b/tests/DatabaseTest.php @@ -24,7 +24,7 @@ public function testIpv4CountryCode() { $this->assertEquals( 'US', - $records['countryCode'], + $records['countryCode'] ); } @@ -35,7 +35,7 @@ public function testIpv4CountryName() { $this->assertEquals( 'United States of America', - $records['countryName'], + $records['countryName'] ); } @@ -54,7 +54,7 @@ public function testIpv6CountryCode() { $this->assertEquals( 'US', - $records['countryCode'], + $records['countryCode'] ); } @@ -65,7 +65,7 @@ public function testIpv6CountryName() { $this->assertEquals( 'United States of America', - $records['countryName'], + $records['countryName'] ); } diff --git a/tests/IpToolsTest.php b/tests/IpToolsTest.php index fcb150a..53d36b8 100644 --- a/tests/IpToolsTest.php +++ b/tests/IpToolsTest.php @@ -139,7 +139,7 @@ public function testCidrToIpv6() $this->assertEqualsCanonicalizing( [ - 'ip_start' => '2002:0000:0000:1234:abcd:ffff:c0a8:0101', + 'ip_start' => '2002:0000:0000:1234:0000:0000:0000:0000', 'ip_end' => '2002:0000:0000:1234:ffff:ffff:ffff:ffff', ], $ipTools->cidrToIpv6('2002::1234:abcd:ffff:c0a8:101/64') diff --git a/tests/RegionTest.php b/tests/RegionTest.php index b6fac9d..76394a8 100644 --- a/tests/RegionTest.php +++ b/tests/RegionTest.php @@ -14,7 +14,7 @@ public function testRegionCodeField() $this->assertEquals( 'US-CA', - $region->getRegionCode('US', 'California'), + $region->getRegionCode('US', 'California') ); } } diff --git a/tests/WebServiceTest.php b/tests/WebServiceTest.php index f973e28..bfc90e4 100644 --- a/tests/WebServiceTest.php +++ b/tests/WebServiceTest.php @@ -11,7 +11,12 @@ class WebServiceTest extends TestCase { public function testCredit() { $ws = new \IP2Location\WebService('demo', 'WS24', true); - $this->assertMatchesRegularExpression('/^[0-9]+$/', (string) $ws->getCredit()); + if (method_exists($this, 'assertMatchesRegularExpression')) { + $this->assertMatchesRegularExpression('/^[0-9]+$/', (string) $ws->getCredit()); + }else{ + // Compatible with php 7.2 && phpunit 8.x + $this->assertRegExp('/^[0-9]+$/', (string) $ws->getCredit()); + } } public function testCountryCode() { @@ -23,7 +28,7 @@ public function testCountryCode() { $this->assertEquals( 'US', - $records['country_code'], + $records['country_code'] ); } }