From ab7a137afa78da812d2b3023d7083af0bee14e82 Mon Sep 17 00:00:00 2001 From: Daniel Ziegenberg Date: Sun, 10 Mar 2024 04:59:51 +0100 Subject: [PATCH] MDL-80635 libraries: Upgrade GeoIP2 and Maxmind DB Reader - GeoIP2 PHP API 3.0.0 - MaxMind DB Reader API 1.11.1 Signed-off-by: Daniel Ziegenberg --- lib/maxmind/GeoIp2/CHANGELOG.md | 344 +++++++++++++ lib/maxmind/GeoIp2/Database/Reader.php | 27 +- .../Exception/AddressNotFoundException.php | 5 +- .../Exception/AuthenticationException.php | 5 +- .../GeoIp2/Exception/GeoIp2Exception.php | 5 +- .../GeoIp2/Exception/HttpException.php | 4 +- .../Exception/InvalidRequestException.php | 4 +- .../Exception/OutOfQueriesException.php | 5 +- lib/maxmind/GeoIp2/LICENSE | 202 ++++++++ lib/maxmind/GeoIp2/Model/AbstractModel.php | 68 --- lib/maxmind/GeoIp2/Model/AnonymousIp.php | 110 +++-- lib/maxmind/GeoIp2/Model/Asn.php | 60 ++- lib/maxmind/GeoIp2/Model/City.php | 132 +++-- lib/maxmind/GeoIp2/Model/ConnectionType.php | 48 +- lib/maxmind/GeoIp2/Model/Country.php | 105 ++-- lib/maxmind/GeoIp2/Model/Domain.php | 48 +- lib/maxmind/GeoIp2/Model/Enterprise.php | 5 +- lib/maxmind/GeoIp2/Model/Insights.php | 5 +- lib/maxmind/GeoIp2/Model/Isp.php | 111 +++-- lib/maxmind/GeoIp2/README.md | 453 ++++++++++++++++++ .../GeoIp2/Record/AbstractNamedRecord.php | 49 ++ .../GeoIp2/Record/AbstractPlaceRecord.php | 67 +-- lib/maxmind/GeoIp2/Record/AbstractRecord.php | 67 --- lib/maxmind/GeoIp2/Record/City.php | 23 +- lib/maxmind/GeoIp2/Record/Continent.php | 56 ++- lib/maxmind/GeoIp2/Record/Country.php | 63 +-- lib/maxmind/GeoIp2/Record/Location.php | 129 +++-- lib/maxmind/GeoIp2/Record/MaxMind.php | 27 +- lib/maxmind/GeoIp2/Record/Postal.php | 50 +- .../GeoIp2/Record/RepresentedCountry.php | 39 +- lib/maxmind/GeoIp2/Record/Subdivision.php | 52 +- lib/maxmind/GeoIp2/Record/Traits.php | 412 ++++++++++------ lib/maxmind/GeoIp2/Util.php | 1 + lib/maxmind/GeoIp2/WebService/Client.php | 34 +- lib/maxmind/GeoIp2/composer.json | 32 ++ lib/maxmind/MaxMind/CHANGELOG.md | 9 +- lib/maxmind/MaxMind/Db/Reader.php | 102 ++-- lib/maxmind/MaxMind/Db/Reader/Decoder.php | 119 +++-- .../Db/Reader/InvalidDatabaseException.php | 7 +- lib/maxmind/MaxMind/Db/Reader/Metadata.php | 14 +- lib/maxmind/MaxMind/Db/Reader/Util.php | 3 +- lib/maxmind/MaxMind/README.md | 4 +- lib/maxmind/MaxMind/autoload.php | 2 +- lib/maxmind/MaxMind/composer.json | 2 +- lib/maxmind/readme_moodle.txt | 5 + lib/thirdpartylibs.xml | 4 +- 46 files changed, 2213 insertions(+), 905 deletions(-) create mode 100644 lib/maxmind/GeoIp2/CHANGELOG.md create mode 100644 lib/maxmind/GeoIp2/LICENSE delete mode 100644 lib/maxmind/GeoIp2/Model/AbstractModel.php create mode 100644 lib/maxmind/GeoIp2/README.md create mode 100644 lib/maxmind/GeoIp2/Record/AbstractNamedRecord.php delete mode 100644 lib/maxmind/GeoIp2/Record/AbstractRecord.php create mode 100644 lib/maxmind/GeoIp2/composer.json diff --git a/lib/maxmind/GeoIp2/CHANGELOG.md b/lib/maxmind/GeoIp2/CHANGELOG.md new file mode 100644 index 00000000000..fe872d0fabe --- /dev/null +++ b/lib/maxmind/GeoIp2/CHANGELOG.md @@ -0,0 +1,344 @@ +CHANGELOG +========= + +3.0.0 (2023-12-04) +------------------ + +* IMPORTANT: PHP 8.1 or greater is now required. +* BREAKING: Read-only properties are now used for the model and record + classes rather than magic methods. This significantly improves performance. +* BREAKING: The `raw` property on model classess and the `record` property on + record classes have been removed. +* BREAKING: On `GeoIp2\Record\Traits`, the deprecated `isAnonymousProxy` and + `isSatelliteProvider` properties have been removed. +* BREAKING: The `jsonSerialize` output has changed. +* `GeoIp2\WebService\Client` methods now throw an `InvalidArgumentException` + if an invalid IP address is passed to them. Previously, they would make + a request to the web service and throw a + `GeoIp2\Exception\InvalidRequestException`. +* The `isAnycast` property was added to `GeoIp2\Record\Traits`. This returns + `true` if the IP address belongs to an [anycast + network](https://en.wikipedia.org/wiki/Anycast). This is available for the + GeoIP2 Country, City Plus, and Insights web services and the GeoIP2 Country, + City, and Enterprise databases. + +2.13.0 (2022-08-05) +------------------- + +* The model class names are no longer constructed by concatenating strings. + This change was made to improve support for tools like PHP-Scoper. + Reported by Andrew Mead. GitHub #194. +* Box 4.0.1 is now used to generate the `geoip2.phar` file. + +2.12.2 (2021-11-30) +------------------- + +* The `geoip2.phar` now works when included from another directory. + Reported by Eduardo Ruiz. GitHub #179. + +2.12.1 (2021-11-23) +------------------- + +* The `geoip2.phar` included in 2.12.0 would only work in CLI applications. + This was due to a change in Box 3.x. The Phar should now work in all + applications. This release only affects users of the Phar file. + +2.12.0 (2021-11-18) +------------------- + +* Support for mobile country code (MCC) and mobile network codes (MNC) was + added for the GeoIP2 ISP and Enterprise databases as well as the GeoIP2 + City and Insights web services. `$mobileCountryCode` and + `$mobileNetworkCode` properties were added to `GeoIp2\Model\Isp` + for the GeoIP2 ISP database and `GeoIp2\Record\Traits` for the Enterprise + database and the GeoIP2 City and Insights web services. We expect this data + to be available by late January, 2022. +* `geoip2.phar` is now generated with Box 3.x. + +2.11.0 (2020-10-01) +------------------- + +* IMPORTANT: PHP 7.2 or greater is now required. +* Added the `isResidentialProxy` property to `GeoIp2\Model\AnonymousIP` and + `GeoIp2\Record\Traits`. +* Additional type hints have been added. + +2.10.0 (2019-12-12) +------------------- + +* PHP 5.6 or greater is now required. +* The `network` property was added to `GeoIp2\Record\Traits`, + `GeoIp2\Model\AnonymousIp`, `GeoIp2\Model\Asn`, + `GeoIp2\Model\ConnectionType`, `Geoip2\Model\Domain`, + and `GeoIp2\Model\Isp`. This is a string in CIDR format representing the + largest network where all of the properties besides `ipAddress` have the + same value. +* Updated documentation of anonymizer properties - `isAnonymousVpn` + and `isHostingProvider` - to be more descriptive. +* The `userCount` property was added to `GeoIp2\Record\Traits`. This is an + integer which indicates the estimated number of users sharing the + IP/network during the past 24 hours. This output is available from GeoIP2 + Precision Insights. +* The `staticIpScore` property was added to `GeoIp2\Record\Traits`. This is + a float which indicates how static or dynamic an IP address is. This + output is available from GeoIP2 Precision Insights. + +2.9.0 (2018-04-10) +------------------ + +* Refer to account IDs using the terminology "account" rather than "user". + +2.8.0 (2018-01-18) +------------------ + +* The `isInEuropeanUnion` property was added to `GeoIp2\Record\Country` + and `GeoIp2\Record\RepresentedCountry`. This property is `true` if the + country is a member state of the European Union. + +2.7.0 (2017-10-27) +------------------ + +* The following new anonymizer properties were added to `GeoIp2\Record\Traits` + for use with GeoIP2 Precision Insights: `isAnonymous`, `isAnonymousVpn`, + `isHostingProvider`, `isPublicProxy`, and `isTorExitNode`. + +2.6.0 (2017-07-10) +----------------- + +* Code clean-up and tidying. +* Set minimum required PHP version to 5.4 in `composer.json`. Previously, + 5.3 would work but was not tested. Now 5.4 is hard minimum version. + +2.5.0 (2017-05-08) +------------------ + +* Support for PHP 5.3 was dropped. +* Added support for GeoLite2 ASN database. + +2.4.5 (2017-01-31) +------------------ + +* Additional error checking on the data returned from `MaxMind\Db\Reader` + was added to help detect corrupt databases. GitHub #83. + +2.4.4 (2016-10-11) +------------------ + +* `isset()` on `mostSpecificSubdivision` attribute now returns the + correct value. Reported by Juan Francisco Giordana. GitHub #81. + +2.4.3 (2016-10-11) +------------------ + +* `isset()` on `name` attribute now returns the correct value. Reported by + Juan Francisco Giordana. GitHub #79. + +2.4.2 (2016-08-17) +------------------ + +* Updated documentation to clarify what the accuracy radius refers to. +* Upgraded `maxmind/web-service-common` to 0.3.0. This version uses + `composer/ca-bundle` rather than our own CA bundle. GitHub #75. +* Improved PHP documentation generation. + +2.4.1 (2016-06-10) +------------------ + +* Corrected type annotations in documentation. GitHub #66. +* Updated documentation to reflect that the accuracy radius is now included + in City. +* Upgraded web service client, which supports setting a proxy. GitHub #59. + +2.4.0 (2016-04-15) +------------------ + +* Added support for the GeoIP2 Enterprise database. + +2.3.3 (2015-09-24) +------------------ + +* Corrected case on `JsonSerializable` interface. Reported by Axel Etcheverry. + GitHub #56. + +2.3.2 (2015-09-23) +------------------ + +* `JsonSerializable` compatibility interface was moved to `GeoIp2\Compat` + rather than the global namespace to prevent autoloading issues. Reported by + Tomas Buteler. GitHub #54. +* Missing documentation for the `$postal` property was added to the + `GeoIp2\Model\City` class. Fix by Roy Sindre Norangshol. GitHub #51. +* In the Phar distribution, source files for this module no longer have their + documentation stripped, allowing IDE introspection to work properly. + Reported by Dominic Black. GitHub #52. + +2.3.1 (2015-06-30) +------------------ + +* Updated `maxmind/web-service-common` to version with fixes for PHP 5.3 and + 5.4. + +2.3.0 (2015-06-29) +------------------ + +* Support for demographics fields `averageIncome` and `populationDensity` in + the `Location` record, returned by the Insights endpoint. +* The `isAnonymousProxy` and `isSatelliteProvider` properties on + `GeoIP2\Record\Traits` have been deprecated. Please use our [GeoIP2 + Anonymous IP database](https://www.maxmind.com/en/geoip2-anonymous-ip-database) + to determine whether an IP address is used by an anonymizing service. + +2.2.0-beta1 (2015-06-09) +------------------------ + +* Typo fix in documentation. + +2.2.0-alpha2 (2015-06-01) +------------------------- + +* `maxmind-ws/web-service-common` was renamed to `maxmind/web-service-common`. + +2.2.0-alpha1 (2015-05-22) +------------------------- + +* The library no longer uses Guzzle and instead uses curl directly. +* Support for `timeout` and `connectTimout` were added to the `$options` array + passed to the `GeoIp2\WebService\Client` constructor. Pull request by Will + Bradley. GitHub #36. + +2.1.1 (2014-12-03) +------------------ + +* The 2.1.0 Phar builds included a shebang line, causing issues when loading + it as a library. This has been corrected. GitHub #33. + +2.1.0 (2014-10-29) +------------------ + +* Update ApiGen dependency to version that isn't broken on case sensitive + file systems. +* Added support for the GeoIP2 Anonymous IP database. The + `GeoIP2\Database\Reader` class now has an `anonymousIp` method which returns + a `GeoIP2\Model\AnonymousIp` object. +* Boolean attributes like those in the `GeoIP2\Record\Traits` class now return + `false` instead of `null` when they were not true. + +2.0.0 (2014-09-22) +------------------ + +* First production release. + +0.9.0 (2014-09-15) +------------------ + +* IMPORTANT: The deprecated `omni()` and `cityIspOrg()` methods have been + removed from `GeoIp2\WebService\Client`. + +0.8.1 (2014-09-12) +------------------ + +* The check added to the `GeoIP2\Database\Reader` lookup methods in 0.8.0 did + not work with the GeoIP2 City Database Subset by Continent with World + Countries. This has been fixed. Fixes GitHub issue #23. + +0.8.0 (2014-09-10) +------------------ + +* The `GeoIp2\Database\Reader` lookup methods (e.g., `city()`, `isp()`) now + throw a `BadMethodCallException` if they are used with a database that + does not match the method. In particular, doing a `city()` lookup on a + GeoIP2 Country database will result in an exception, and vice versa. +* A `metadata()` method has been added to the `GeoIP2\Database\Reader` class. + This returns a `MaxMind\Db\Reader\Metadata` class with information about the + database. +* The name attribute was missing from the RepresentedCountry class. + +0.7.0 (2014-07-22) +------------------ + +* The web service client API has been updated for the v2.1 release of the web + service. In particular, the `cityIspOrg` and `omni` methods on + `GeoIp2\WebService\Client` should be considered deprecated. The `city` + method now provides all of the data formerly provided by `cityIspOrg`, and + the `omni` method has been replaced by the `insights` method. +* Support was added for GeoIP2 Connection Type, Domain and ISP databases. + + +0.6.3 (2014-05-12) +------------------ + +* With the previous Phar builds, some users received `phar error: invalid url + or non-existent phar` errors. The correct alias is now used for the Phar, + and this should no longer be an issue. + +0.6.2 (2014-05-08) +------------------ + +* The Phar build was broken with Guzzle 3.9.0+. This has been fixed. + +0.6.1 (2014-05-01) +------------------ + +* This API now officially supports HHVM. +* The `maxmind-db/reader` dependency was updated to a version that does not + require BC Math. +* The Composer compatibility autoload rules are now targeted more narrowly. +* A `box.json` file is included to build a Phar package. + +0.6.0 (2014-02-19) +------------------ + +* This API is now licensed under the Apache License, Version 2.0. +* Model and record classes now implement `JsonSerializable`. +* `isset` now works with model and record classes. + +0.5.0 (2013-10-21) +------------------ + +* Renamed $languages constructor parameters to $locales for both the Client + and Reader classes. +* Documentation and code clean-up (Ben Morel). +* Added the interface `GeoIp2\ProviderInterface`, which is implemented by both + `\GeoIp2\Database\Reader` and `\GeoIp2\WebService\Client`. + +0.4.0 (2013-07-16) +------------------ + +* This is the first release with the GeoIP2 database reader. Please see the + `README.md` file and the `\GeoIp2\Database\Reader` class. +* The general exception classes were replaced with specific exception classes + representing particular types of errors, such as an authentication error. + +0.3.0 (2013-07-12) +------------------ + +* In namespaces and class names, "GeoIP2" was renamed to "GeoIp2" to improve + consistency. + +0.2.1 (2013-06-10) +------------------ + +* First official beta release. +* Documentation updates and corrections. + +0.2.0 (2013-05-29) +------------------ + +* `GenericException` was renamed to `GeoIP2Exception`. +* We now support more languages. The new languages are de, es, fr, and pt-BR. +* The REST API now returns a record with data about your account. There is + a new `GeoIP\Records\MaxMind` class for this data. +* The `continentCode` attribute on `Continent` was renamed to `code`. +* Documentation updates. + +0.1.1 (2013-05-14) +------------------ + +* Updated Guzzle version requirement. +* Fixed Composer example in README.md. + + +0.1.0 (2013-05-13) +------------------ + +* Initial release. diff --git a/lib/maxmind/GeoIp2/Database/Reader.php b/lib/maxmind/GeoIp2/Database/Reader.php index 4dabc5d15c4..1570afb8dc6 100644 --- a/lib/maxmind/GeoIp2/Database/Reader.php +++ b/lib/maxmind/GeoIp2/Database/Reader.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace GeoIp2\Database; use GeoIp2\Exception\AddressNotFoundException; -use GeoIp2\Model\AbstractModel; use GeoIp2\Model\AnonymousIp; use GeoIp2\Model\Asn; use GeoIp2\Model\City; @@ -44,20 +43,14 @@ use MaxMind\Db\Reader\InvalidDatabaseException; */ class Reader implements ProviderInterface { - /** - * @var DbReader - */ - private $dbReader; + private DbReader $dbReader; - /** - * @var string - */ - private $dbType; + private string $dbType; /** * @var array */ - private $locales; + private array $locales; /** * Constructor. @@ -90,7 +83,6 @@ class Reader implements ProviderInterface */ public function city(string $ipAddress): City { - // @phpstan-ignore-next-line return $this->modelFor(City::class, 'City', $ipAddress); } @@ -106,7 +98,6 @@ class Reader implements ProviderInterface */ public function country(string $ipAddress): Country { - // @phpstan-ignore-next-line return $this->modelFor(Country::class, 'Country', $ipAddress); } @@ -122,7 +113,6 @@ class Reader implements ProviderInterface */ public function anonymousIp(string $ipAddress): AnonymousIp { - // @phpstan-ignore-next-line return $this->flatModelFor( AnonymousIp::class, 'GeoIP2-Anonymous-IP', @@ -142,7 +132,6 @@ class Reader implements ProviderInterface */ public function asn(string $ipAddress): Asn { - // @phpstan-ignore-next-line return $this->flatModelFor( Asn::class, 'GeoLite2-ASN', @@ -162,7 +151,6 @@ class Reader implements ProviderInterface */ public function connectionType(string $ipAddress): ConnectionType { - // @phpstan-ignore-next-line return $this->flatModelFor( ConnectionType::class, 'GeoIP2-Connection-Type', @@ -182,7 +170,6 @@ class Reader implements ProviderInterface */ public function domain(string $ipAddress): Domain { - // @phpstan-ignore-next-line return $this->flatModelFor( Domain::class, 'GeoIP2-Domain', @@ -202,7 +189,6 @@ class Reader implements ProviderInterface */ public function enterprise(string $ipAddress): Enterprise { - // @phpstan-ignore-next-line return $this->modelFor(Enterprise::class, 'Enterprise', $ipAddress); } @@ -218,7 +204,6 @@ class Reader implements ProviderInterface */ public function isp(string $ipAddress): Isp { - // @phpstan-ignore-next-line return $this->flatModelFor( Isp::class, 'GeoIP2-ISP', @@ -226,7 +211,7 @@ class Reader implements ProviderInterface ); } - private function modelFor(string $class, string $type, string $ipAddress): AbstractModel + private function modelFor(string $class, string $type, string $ipAddress): object { [$record, $prefixLen] = $this->getRecord($class, $type, $ipAddress); @@ -236,7 +221,7 @@ class Reader implements ProviderInterface return new $class($record, $this->locales); } - private function flatModelFor(string $class, string $type, string $ipAddress): AbstractModel + private function flatModelFor(string $class, string $type, string $ipAddress): object { [$record, $prefixLen] = $this->getRecord($class, $type, $ipAddress); @@ -248,7 +233,7 @@ class Reader implements ProviderInterface private function getRecord(string $class, string $type, string $ipAddress): array { - if (strpos($this->dbType, $type) === false) { + if (!str_contains($this->dbType, $type)) { $method = lcfirst((new \ReflectionClass($class))->getShortName()); throw new \BadMethodCallException( diff --git a/lib/maxmind/GeoIp2/Exception/AddressNotFoundException.php b/lib/maxmind/GeoIp2/Exception/AddressNotFoundException.php index 628fb0672a1..acc251866b9 100644 --- a/lib/maxmind/GeoIp2/Exception/AddressNotFoundException.php +++ b/lib/maxmind/GeoIp2/Exception/AddressNotFoundException.php @@ -7,6 +7,5 @@ namespace GeoIp2\Exception; /** * This class represents a generic error. */ -class AddressNotFoundException extends GeoIp2Exception -{ -} +// phpcs:disable +class AddressNotFoundException extends GeoIp2Exception {} diff --git a/lib/maxmind/GeoIp2/Exception/AuthenticationException.php b/lib/maxmind/GeoIp2/Exception/AuthenticationException.php index 9b57a11fa7f..d0f678b4f11 100644 --- a/lib/maxmind/GeoIp2/Exception/AuthenticationException.php +++ b/lib/maxmind/GeoIp2/Exception/AuthenticationException.php @@ -7,6 +7,5 @@ namespace GeoIp2\Exception; /** * This class represents a generic error. */ -class AuthenticationException extends GeoIp2Exception -{ -} +// phpcs:disable +class AuthenticationException extends GeoIp2Exception {} diff --git a/lib/maxmind/GeoIp2/Exception/GeoIp2Exception.php b/lib/maxmind/GeoIp2/Exception/GeoIp2Exception.php index a96d62065e4..93b4f425b5f 100644 --- a/lib/maxmind/GeoIp2/Exception/GeoIp2Exception.php +++ b/lib/maxmind/GeoIp2/Exception/GeoIp2Exception.php @@ -7,6 +7,5 @@ namespace GeoIp2\Exception; /** * This class represents a generic error. */ -class GeoIp2Exception extends \Exception -{ -} +// phpcs:disable +class GeoIp2Exception extends \Exception {} diff --git a/lib/maxmind/GeoIp2/Exception/HttpException.php b/lib/maxmind/GeoIp2/Exception/HttpException.php index c11d4410880..f56923e2de8 100644 --- a/lib/maxmind/GeoIp2/Exception/HttpException.php +++ b/lib/maxmind/GeoIp2/Exception/HttpException.php @@ -11,10 +11,8 @@ class HttpException extends GeoIp2Exception { /** * The URI queried. - * - * @var string */ - public $uri; + public string $uri; public function __construct( string $message, diff --git a/lib/maxmind/GeoIp2/Exception/InvalidRequestException.php b/lib/maxmind/GeoIp2/Exception/InvalidRequestException.php index 925b68df00b..5a58ff9513d 100644 --- a/lib/maxmind/GeoIp2/Exception/InvalidRequestException.php +++ b/lib/maxmind/GeoIp2/Exception/InvalidRequestException.php @@ -12,10 +12,8 @@ class InvalidRequestException extends HttpException { /** * The code returned by the MaxMind web service. - * - * @var string */ - public $error; + public string $error; public function __construct( string $message, diff --git a/lib/maxmind/GeoIp2/Exception/OutOfQueriesException.php b/lib/maxmind/GeoIp2/Exception/OutOfQueriesException.php index 9734c8ce403..8e866f7c448 100644 --- a/lib/maxmind/GeoIp2/Exception/OutOfQueriesException.php +++ b/lib/maxmind/GeoIp2/Exception/OutOfQueriesException.php @@ -7,6 +7,5 @@ namespace GeoIp2\Exception; /** * This class represents a generic error. */ -class OutOfQueriesException extends GeoIp2Exception -{ -} +// phpcs:disable +class OutOfQueriesException extends GeoIp2Exception {} diff --git a/lib/maxmind/GeoIp2/LICENSE b/lib/maxmind/GeoIp2/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/lib/maxmind/GeoIp2/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/maxmind/GeoIp2/Model/AbstractModel.php b/lib/maxmind/GeoIp2/Model/AbstractModel.php deleted file mode 100644 index 27f3e2a4400..00000000000 --- a/lib/maxmind/GeoIp2/Model/AbstractModel.php +++ /dev/null @@ -1,68 +0,0 @@ - - */ - protected $raw; - - /** - * @ignore - */ - public function __construct(array $raw) - { - $this->raw = $raw; - } - - /** - * @ignore - * - * @return mixed - */ - protected function get(string $field) - { - if (isset($this->raw[$field])) { - return $this->raw[$field]; - } - if (preg_match('/^is_/', $field)) { - return false; - } - - return null; - } - - /** - * @ignore - * - * @return mixed - */ - public function __get(string $attr) - { - if ($attr !== 'instance' && property_exists($this, $attr)) { - return $this->{$attr}; - } - - throw new \RuntimeException("Unknown attribute: $attr"); - } - - /** - * @ignore - */ - public function __isset(string $attr): bool - { - return $attr !== 'instance' && isset($this->{$attr}); - } - - public function jsonSerialize(): array - { - return $this->raw; - } -} diff --git a/lib/maxmind/GeoIp2/Model/AnonymousIp.php b/lib/maxmind/GeoIp2/Model/AnonymousIp.php index 5586bd00fcb..7e3b9b6ea76 100644 --- a/lib/maxmind/GeoIp2/Model/AnonymousIp.php +++ b/lib/maxmind/GeoIp2/Model/AnonymousIp.php @@ -8,84 +8,100 @@ use GeoIp2\Util; /** * This class provides the GeoIP2 Anonymous IP model. - * - * @property-read bool $isAnonymous This is true if the IP address belongs to - * any sort of anonymous network. - * @property-read bool $isAnonymousVpn This is true if the IP address is - * registered to an anonymous VPN provider. If a VPN provider does not - * register subnets under names associated with them, we will likely only - * flag their IP ranges using the isHostingProvider property. - * @property-read bool $isHostingProvider This is true if the IP address belongs - * to a hosting or VPN provider (see description of isAnonymousVpn property). - * @property-read bool $isPublicProxy This is true if the IP address belongs to - * a public proxy. - * @property-read bool $isResidentialProxy This is true if the IP address is - * on a suspected anonymizing network and belongs to a residential ISP. - * @property-read bool $isTorExitNode This is true if the IP address is a Tor - * exit node. - * @property-read string $ipAddress The IP address that the data in the model is - * for. - * @property-read string $network The network in CIDR notation associated with - * the record. In particular, this is the largest network where all of the - * fields besides $ipAddress have the same value. */ -class AnonymousIp extends AbstractModel +class AnonymousIp implements \JsonSerializable { /** - * @var bool + * @var bool this is true if the IP address belongs to + * any sort of anonymous network */ - protected $isAnonymous; + public readonly bool $isAnonymous; /** - * @var bool + * @var bool This is true if the IP address is + * registered to an anonymous VPN provider. If a VPN provider does not + * register subnets under names associated with them, we will likely only + * flag their IP ranges using the isHostingProvider property. */ - protected $isAnonymousVpn; + public readonly bool $isAnonymousVpn; /** - * @var bool + * @var bool this is true if the IP address belongs + * to a hosting or VPN provider (see description of isAnonymousVpn property) */ - protected $isHostingProvider; + public readonly bool $isHostingProvider; /** - * @var bool + * @var bool this is true if the IP address belongs to + * a public proxy */ - protected $isPublicProxy; + public readonly bool $isPublicProxy; /** - * @var bool + * @var bool this is true if the IP address is + * on a suspected anonymizing network and belongs to a residential ISP */ - protected $isResidentialProxy; + public readonly bool $isResidentialProxy; /** - * @var bool + * @var bool this is true if the IP address is a Tor + * exit node */ - protected $isTorExitNode; + public readonly bool $isTorExitNode; /** - * @var string + * @var string the IP address that the data in the model is + * for */ - protected $ipAddress; + public readonly string $ipAddress; /** - * @var string + * @var string The network in CIDR notation associated with + * the record. In particular, this is the largest network where all of the + * fields besides $ipAddress have the same value. */ - protected $network; + public readonly string $network; /** * @ignore */ public function __construct(array $raw) { - parent::__construct($raw); - - $this->isAnonymous = $this->get('is_anonymous'); - $this->isAnonymousVpn = $this->get('is_anonymous_vpn'); - $this->isHostingProvider = $this->get('is_hosting_provider'); - $this->isPublicProxy = $this->get('is_public_proxy'); - $this->isResidentialProxy = $this->get('is_residential_proxy'); - $this->isTorExitNode = $this->get('is_tor_exit_node'); - $ipAddress = $this->get('ip_address'); + $this->isAnonymous = $raw['is_anonymous'] ?? false; + $this->isAnonymousVpn = $raw['is_anonymous_vpn'] ?? false; + $this->isHostingProvider = $raw['is_hosting_provider'] ?? false; + $this->isPublicProxy = $raw['is_public_proxy'] ?? false; + $this->isResidentialProxy = $raw['is_residential_proxy'] ?? false; + $this->isTorExitNode = $raw['is_tor_exit_node'] ?? false; + $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; - $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); + $this->network = Util::cidr($ipAddress, $raw['prefix_len']); + } + + public function jsonSerialize(): ?array + { + $js = []; + if ($this->isAnonymous !== null) { + $js['is_anonymous'] = $this->isAnonymous; + } + if ($this->isAnonymousVpn !== null) { + $js['is_anonymous_vpn'] = $this->isAnonymousVpn; + } + if ($this->isHostingProvider !== null) { + $js['is_hosting_provider'] = $this->isHostingProvider; + } + if ($this->isPublicProxy !== null) { + $js['is_public_proxy'] = $this->isPublicProxy; + } + if ($this->isResidentialProxy !== null) { + $js['is_residential_proxy'] = $this->isResidentialProxy; + } + if ($this->isTorExitNode !== null) { + $js['is_tor_exit_node'] = $this->isTorExitNode; + } + $js['ip_address'] = $this->ipAddress; + $js['network'] = $this->network; + + return $js; } } diff --git a/lib/maxmind/GeoIp2/Model/Asn.php b/lib/maxmind/GeoIp2/Model/Asn.php index f05177eb906..55b85c5e321 100644 --- a/lib/maxmind/GeoIp2/Model/Asn.php +++ b/lib/maxmind/GeoIp2/Model/Asn.php @@ -8,51 +8,61 @@ use GeoIp2\Util; /** * This class provides the GeoLite2 ASN model. - * - * @property-read int|null $autonomousSystemNumber The autonomous system number - * associated with the IP address. - * @property-read string|null $autonomousSystemOrganization The organization - * associated with the registered autonomous system number for the IP - * address. - * @property-read string $ipAddress The IP address that the data in the model is - * for. - * @property-read string $network The network in CIDR notation associated with - * the record. In particular, this is the largest network where all of the - * fields besides $ipAddress have the same value. */ -class Asn extends AbstractModel +class Asn implements \JsonSerializable { /** - * @var int|null + * @var int|null the autonomous system number + * associated with the IP address */ - protected $autonomousSystemNumber; + public readonly ?int $autonomousSystemNumber; /** - * @var string|null + * @var string|null the organization + * associated with the registered autonomous system number for the IP + * address */ - protected $autonomousSystemOrganization; + public readonly ?string $autonomousSystemOrganization; /** - * @var string + * @var string the IP address that the data in the model is + * for */ - protected $ipAddress; + public readonly string $ipAddress; /** - * @var string + * @var string The network in CIDR notation associated with + * the record. In particular, this is the largest network where all of the + * fields besides $ipAddress have the same value. */ - protected $network; + public readonly string $network; /** * @ignore */ public function __construct(array $raw) { - parent::__construct($raw); - $this->autonomousSystemNumber = $this->get('autonomous_system_number'); + $this->autonomousSystemNumber = $raw['autonomous_system_number'] ?? null; $this->autonomousSystemOrganization = - $this->get('autonomous_system_organization'); - $ipAddress = $this->get('ip_address'); + $raw['autonomous_system_organization'] ?? null; + $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; - $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); + $this->network = Util::cidr($ipAddress, $raw['prefix_len']); + } + + public function jsonSerialize(): ?array + { + $js = []; + + if ($this->autonomousSystemNumber !== null) { + $js['autonomous_system_number'] = $this->autonomousSystemNumber; + } + if ($this->autonomousSystemOrganization !== null) { + $js['autonomous_system_organization'] = $this->autonomousSystemOrganization; + } + $js['ip_address'] = $this->ipAddress; + $js['network'] = $this->network; + + return $js; } } diff --git a/lib/maxmind/GeoIp2/Model/City.php b/lib/maxmind/GeoIp2/Model/City.php index b2e81dccb56..f89ccfe40d7 100644 --- a/lib/maxmind/GeoIp2/Model/City.php +++ b/lib/maxmind/GeoIp2/Model/City.php @@ -10,54 +10,45 @@ namespace GeoIp2\Model; * * See https://dev.maxmind.com/geoip/docs/web-services?lang=en for more * details. - * - * @property-read \GeoIp2\Record\City $city City data for the requested IP - * address. - * @property-read \GeoIp2\Record\Location $location Location data for the - * requested IP address. - * @property-read \GeoIp2\Record\Postal $postal Postal data for the - * requested IP address. - * @property-read array $subdivisions An array \GeoIp2\Record\Subdivision - * objects representing the country subdivisions for the requested IP - * address. The number and type of subdivisions varies by country, but a - * subdivision is typically a state, province, county, etc. Subdivisions - * are ordered from most general (largest) to most specific (smallest). - * If the response did not contain any subdivisions, this method returns - * an empty array. - * @property-read \GeoIp2\Record\Subdivision $mostSpecificSubdivision An object - * representing the most specific subdivision returned. If the response - * did not contain any subdivisions, this method returns an empty - * \GeoIp2\Record\Subdivision object. */ class City extends Country { /** - * @ignore - * - * @var \GeoIp2\Record\City + * @var \GeoIp2\Record\City city data for the requested IP + * address */ - protected $city; + public readonly \GeoIp2\Record\City $city; /** - * @ignore - * - * @var \GeoIp2\Record\Location + * @var \GeoIp2\Record\Location location data for the + * requested IP address */ - protected $location; + public readonly \GeoIp2\Record\Location $location; /** - * @ignore - * - * @var \GeoIp2\Record\Postal + * @var \GeoIp2\Record\Subdivision An object + * representing the most specific subdivision returned. If the response + * did not contain any subdivisions, this method returns an empty + * \GeoIp2\Record\Subdivision object. */ - protected $postal; + public readonly \GeoIp2\Record\Subdivision $mostSpecificSubdivision; /** - * @ignore - * - * @var array<\GeoIp2\Record\Subdivision> + * @var \GeoIp2\Record\Postal postal data for the + * requested IP address */ - protected $subdivisions = []; + public readonly \GeoIp2\Record\Postal $postal; + + /** + * @var array<\GeoIp2\Record\Subdivision> An array of \GeoIp2\Record\Subdivision + * objects representing the country subdivisions for the requested IP + * address. The number and type of subdivisions varies by country, but a + * subdivision is typically a state, province, county, etc. Subdivisions + * are ordered from most general (largest) to most specific (smallest). + * If the response did not contain any subdivisions, this method returns + * an empty array. + */ + public readonly array $subdivisions; /** * @ignore @@ -66,58 +57,59 @@ class City extends Country { parent::__construct($raw, $locales); - $this->city = new \GeoIp2\Record\City($this->get('city'), $locales); - $this->location = new \GeoIp2\Record\Location($this->get('location')); - $this->postal = new \GeoIp2\Record\Postal($this->get('postal')); + $this->city = new \GeoIp2\Record\City($raw['city'] ?? [], $locales); + $this->location = new \GeoIp2\Record\Location($raw['location'] ?? []); + $this->postal = new \GeoIp2\Record\Postal($raw['postal'] ?? []); - $this->createSubdivisions($raw, $locales); - } - - private function createSubdivisions(array $raw, array $locales): void - { if (!isset($raw['subdivisions'])) { + $this->subdivisions = []; + $this->mostSpecificSubdivision = + new \GeoIp2\Record\Subdivision([], $locales); + return; } + $subdivisions = []; foreach ($raw['subdivisions'] as $sub) { - $this->subdivisions[] = + $subdivisions[] = new \GeoIp2\Record\Subdivision($sub, $locales) ; } + + // Not using end as we don't want to modify internal pointer. + $this->mostSpecificSubdivision = + $subdivisions[\count($subdivisions) - 1]; + $this->subdivisions = $subdivisions; } - /** - * @ignore - * - * @return mixed - */ - public function __get(string $attr) + public function jsonSerialize(): ?array { - if ($attr === 'mostSpecificSubdivision') { - return $this->{$attr}(); + $js = parent::jsonSerialize(); + + $city = $this->city->jsonSerialize(); + if (!empty($city)) { + $js['city'] = $city; } - return parent::__get($attr); - } - - /** - * @ignore - */ - public function __isset(string $attr): bool - { - if ($attr === 'mostSpecificSubdivision') { - // We always return a mostSpecificSubdivision, even if it is the - // empty subdivision - return true; + $location = $this->location->jsonSerialize(); + if (!empty($location)) { + $js['location'] = $location; } - return parent::__isset($attr); - } + $postal = + $this->postal->jsonSerialize(); + if (!empty($postal)) { + $js['postal'] = $postal; + } - private function mostSpecificSubdivision(): \GeoIp2\Record\Subdivision - { - return empty($this->subdivisions) ? - new \GeoIp2\Record\Subdivision([], $this->locales) : - end($this->subdivisions); + $subdivisions = []; + foreach ($this->subdivisions as $sub) { + $subdivisions[] = $sub->jsonSerialize(); + } + if (!empty($subdivisions)) { + $js['subdivisions'] = $subdivisions; + } + + return $js; } } diff --git a/lib/maxmind/GeoIp2/Model/ConnectionType.php b/lib/maxmind/GeoIp2/Model/ConnectionType.php index 36d4529a208..6facb5e852b 100644 --- a/lib/maxmind/GeoIp2/Model/ConnectionType.php +++ b/lib/maxmind/GeoIp2/Model/ConnectionType.php @@ -8,43 +8,49 @@ use GeoIp2\Util; /** * This class provides the GeoIP2 Connection-Type model. - * - * @property-read string|null $connectionType The connection type may take the - * following values: "Dialup", "Cable/DSL", "Corporate", "Cellular". - * Additional values may be added in the future. - * @property-read string $ipAddress The IP address that the data in the model is - * for. - * @property-read string $network The network in CIDR notation associated with - * the record. In particular, this is the largest network where all of the - * fields besides $ipAddress have the same value. */ -class ConnectionType extends AbstractModel +class ConnectionType implements \JsonSerializable { /** - * @var string|null + * @var string|null The connection type may take the + * following values: "Dialup", "Cable/DSL", "Corporate", "Cellular", and + * "Satellite". Additional values may be added in the future. */ - protected $connectionType; + public readonly ?string $connectionType; /** - * @var string + * @var string the IP address that the data in the model is + * for */ - protected $ipAddress; + public readonly string $ipAddress; /** - * @var string + * @var string The network in CIDR notation associated with + * the record. In particular, this is the largest network where all of the + * fields besides $ipAddress have the same value. */ - protected $network; + public readonly string $network; /** * @ignore */ public function __construct(array $raw) { - parent::__construct($raw); - - $this->connectionType = $this->get('connection_type'); - $ipAddress = $this->get('ip_address'); + $this->connectionType = $raw['connection_type'] ?? null; + $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; - $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); + $this->network = Util::cidr($ipAddress, $raw['prefix_len']); + } + + public function jsonSerialize(): ?array + { + $js = []; + if ($this->connectionType !== null) { + $js['connection_type'] = $this->connectionType; + } + $js['ip_address'] = $this->ipAddress; + $js['network'] = $this->network; + + return $js; } } diff --git a/lib/maxmind/GeoIp2/Model/Country.php b/lib/maxmind/GeoIp2/Model/Country.php index fdffc63f11c..0fae2db75f0 100644 --- a/lib/maxmind/GeoIp2/Model/Country.php +++ b/lib/maxmind/GeoIp2/Model/Country.php @@ -8,89 +8,102 @@ namespace GeoIp2\Model; * Model class for the data returned by GeoIP2 Country web service and database. * * See https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details. - * - * @property-read \GeoIp2\Record\Continent $continent Continent data for the - * requested IP address. - * @property-read \GeoIp2\Record\Country $country Country data for the requested - * IP address. This object represents the country where MaxMind believes the - * end user is located. - * @property-read \GeoIp2\Record\MaxMind $maxmind Data related to your MaxMind - * account. - * @property-read \GeoIp2\Record\Country $registeredCountry Registered country - * data for the requested IP address. This record represents the country - * where the ISP has registered a given IP block and may differ from the - * user's country. - * @property-read \GeoIp2\Record\RepresentedCountry $representedCountry - * Represented country data for the requested IP address. The represented - * country is used for things like military bases. It is only present when - * the represented country differs from the country. - * @property-read \GeoIp2\Record\Traits $traits Data for the traits of the - * requested IP address. - * @property-read array $raw The raw data from the web service. */ -class Country extends AbstractModel +class Country implements \JsonSerializable { /** - * @var \GeoIp2\Record\Continent + * @var \GeoIp2\Record\Continent continent data for the + * requested IP address */ - protected $continent; + public readonly \GeoIp2\Record\Continent $continent; /** - * @var \GeoIp2\Record\Country + * @var \GeoIp2\Record\Country Country data for the requested + * IP address. This object represents the country where MaxMind believes the + * end user is located. */ - protected $country; + public readonly \GeoIp2\Record\Country $country; /** - * @var array + * @var \GeoIp2\Record\MaxMind data related to your MaxMind + * account */ - protected $locales; + public readonly \GeoIp2\Record\MaxMind $maxmind; /** - * @var \GeoIp2\Record\MaxMind + * @var \GeoIp2\Record\Country Registered country + * data for the requested IP address. This record represents the country + * where the ISP has registered a given IP block and may differ from the + * user's country. */ - protected $maxmind; + public readonly \GeoIp2\Record\Country $registeredCountry; /** - * @var \GeoIp2\Record\Country + * @var \GeoIp2\Record\RepresentedCountry * Represented country data for the requested IP address. The represented + * country is used for things like military bases. It is only present when + * the represented country differs from the country. */ - protected $registeredCountry; + public readonly \GeoIp2\Record\RepresentedCountry $representedCountry; /** - * @var \GeoIp2\Record\RepresentedCountry + * @var \GeoIp2\Record\Traits data for the traits of the + * requested IP address */ - protected $representedCountry; - - /** - * @var \GeoIp2\Record\Traits - */ - protected $traits; + public readonly \GeoIp2\Record\Traits $traits; /** * @ignore */ public function __construct(array $raw, array $locales = ['en']) { - parent::__construct($raw); - $this->continent = new \GeoIp2\Record\Continent( - $this->get('continent'), + $raw['continent'] ?? [], $locales ); $this->country = new \GeoIp2\Record\Country( - $this->get('country'), + $raw['country'] ?? [], $locales ); - $this->maxmind = new \GeoIp2\Record\MaxMind($this->get('maxmind')); + $this->maxmind = new \GeoIp2\Record\MaxMind($raw['maxmind'] ?? []); $this->registeredCountry = new \GeoIp2\Record\Country( - $this->get('registered_country'), + $raw['registered_country'] ?? [], $locales ); $this->representedCountry = new \GeoIp2\Record\RepresentedCountry( - $this->get('represented_country'), + $raw['represented_country'] ?? [], $locales ); - $this->traits = new \GeoIp2\Record\Traits($this->get('traits')); + $this->traits = new \GeoIp2\Record\Traits($raw['traits'] ?? []); + } - $this->locales = $locales; + public function jsonSerialize(): ?array + { + $js = []; + $continent = $this->continent->jsonSerialize(); + if (!empty($continent)) { + $js['continent'] = $continent; + } + $country = $this->country->jsonSerialize(); + if (!empty($country)) { + $js['country'] = $country; + } + $maxmind = $this->maxmind->jsonSerialize(); + if (!empty($maxmind)) { + $js['maxmind'] = $maxmind; + } + $registeredCountry = $this->registeredCountry->jsonSerialize(); + if (!empty($registeredCountry)) { + $js['registered_country'] = $registeredCountry; + } + $representedCountry = $this->representedCountry->jsonSerialize(); + if (!empty($representedCountry)) { + $js['represented_country'] = $representedCountry; + } + $traits = $this->traits->jsonSerialize(); + if (!empty($traits)) { + $js['traits'] = $traits; + } + + return $js; } } diff --git a/lib/maxmind/GeoIp2/Model/Domain.php b/lib/maxmind/GeoIp2/Model/Domain.php index 067a5076ef1..1ce83fdbb09 100644 --- a/lib/maxmind/GeoIp2/Model/Domain.php +++ b/lib/maxmind/GeoIp2/Model/Domain.php @@ -8,43 +8,49 @@ use GeoIp2\Util; /** * This class provides the GeoIP2 Domain model. - * - * @property-read string|null $domain The second level domain associated with the - * IP address. This will be something like "example.com" or - * "example.co.uk", not "foo.example.com". - * @property-read string $ipAddress The IP address that the data in the model is - * for. - * @property-read string $network The network in CIDR notation associated with - * the record. In particular, this is the largest network where all of the - * fields besides $ipAddress have the same value. */ -class Domain extends AbstractModel +class Domain implements \JsonSerializable { /** - * @var string|null + * @var string|null The second level domain associated with the + * IP address. This will be something like "example.com" or + * "example.co.uk", not "foo.example.com". */ - protected $domain; + public readonly ?string $domain; /** - * @var string + * @var string the IP address that the data in the model is + * for */ - protected $ipAddress; + public readonly string $ipAddress; /** - * @var string + * @var string The network in CIDR notation associated with + * the record. In particular, this is the largest network where all of the + * fields besides $ipAddress have the same value. */ - protected $network; + public readonly string $network; /** * @ignore */ public function __construct(array $raw) { - parent::__construct($raw); - - $this->domain = $this->get('domain'); - $ipAddress = $this->get('ip_address'); + $this->domain = $raw['domain'] ?? null; + $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; - $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); + $this->network = Util::cidr($ipAddress, $raw['prefix_len']); + } + + public function jsonSerialize(): ?array + { + $js = []; + if ($this->domain !== null) { + $js['domain'] = $this->domain; + } + $js['ip_address'] = $this->ipAddress; + $js['network'] = $this->network; + + return $js; } } diff --git a/lib/maxmind/GeoIp2/Model/Enterprise.php b/lib/maxmind/GeoIp2/Model/Enterprise.php index c63469ba425..57f10d84512 100644 --- a/lib/maxmind/GeoIp2/Model/Enterprise.php +++ b/lib/maxmind/GeoIp2/Model/Enterprise.php @@ -10,6 +10,5 @@ namespace GeoIp2\Model; * See https://dev.maxmind.com/geoip/docs/web-services?lang=en for more * details. */ -class Enterprise extends City -{ -} +// phpcs:disable +class Enterprise extends City {} diff --git a/lib/maxmind/GeoIp2/Model/Insights.php b/lib/maxmind/GeoIp2/Model/Insights.php index 351c7a5f164..2f5a287599c 100644 --- a/lib/maxmind/GeoIp2/Model/Insights.php +++ b/lib/maxmind/GeoIp2/Model/Insights.php @@ -10,6 +10,5 @@ namespace GeoIp2\Model; * See https://dev.maxmind.com/geoip/docs/web-services?lang=en for * more details. */ -class Insights extends City -{ -} +// phpcs:disable +class Insights extends City {} diff --git a/lib/maxmind/GeoIp2/Model/Isp.php b/lib/maxmind/GeoIp2/Model/Isp.php index f54efd9bd6a..f329063e919 100644 --- a/lib/maxmind/GeoIp2/Model/Isp.php +++ b/lib/maxmind/GeoIp2/Model/Isp.php @@ -8,86 +8,103 @@ use GeoIp2\Util; /** * This class provides the GeoIP2 ISP model. - * - * @property-read int|null $autonomousSystemNumber The autonomous system number - * associated with the IP address. - * @property-read string|null $autonomousSystemOrganization The organization - * associated with the registered autonomous system number for the IP - * address. - * @property-read string|null $isp The name of the ISP associated with the IP - * address. - * @property-read string|null $mobileCountryCode The [mobile country code - * (MCC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with - * the IP address and ISP. - * @property-read string|null $mobileNetworkCode The [mobile network code - * (MNC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with - * the IP address and ISP. - * @property-read string|null $organization The name of the organization associated - * with the IP address. - * @property-read string $ipAddress The IP address that the data in the model is - * for. - * @property-read string $network The network in CIDR notation associated with - * the record. In particular, this is the largest network where all of the - * fields besides $ipAddress have the same value. */ -class Isp extends AbstractModel +class Isp implements \JsonSerializable { /** - * @var int|null + * @var int|null the autonomous system number + * associated with the IP address */ - protected $autonomousSystemNumber; + public readonly ?int $autonomousSystemNumber; /** - * @var string|null + * @var string|null the organization + * associated with the registered autonomous system number for the IP + * address */ - protected $autonomousSystemOrganization; + public readonly ?string $autonomousSystemOrganization; /** - * @var string|null + * @var string|null the name of the ISP associated with the IP + * address */ - protected $isp; + public readonly ?string $isp; /** - * @var string|null + * @var string|null The [mobile country code + * (MCC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with + * the IP address and ISP. */ - protected $mobileCountryCode; + public readonly ?string $mobileCountryCode; /** - * @var string|null + * @var string|null The [mobile network code + * (MNC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with + * the IP address and ISP. */ - protected $mobileNetworkCode; + public readonly ?string $mobileNetworkCode; /** - * @var string|null + * @var string|null the name of the organization associated + * with the IP address */ - protected $organization; + public readonly ?string $organization; /** - * @var string + * @var string the IP address that the data in the model is + * for */ - protected $ipAddress; + public readonly string $ipAddress; /** - * @var string + * @var string The network in CIDR notation associated with + * the record. In particular, this is the largest network where all of the + * fields besides $ipAddress have the same value. */ - protected $network; + public readonly string $network; /** * @ignore */ public function __construct(array $raw) { - parent::__construct($raw); - $this->autonomousSystemNumber = $this->get('autonomous_system_number'); + $this->autonomousSystemNumber = $raw['autonomous_system_number'] ?? null; $this->autonomousSystemOrganization = - $this->get('autonomous_system_organization'); - $this->isp = $this->get('isp'); - $this->mobileCountryCode = $this->get('mobile_country_code'); - $this->mobileNetworkCode = $this->get('mobile_network_code'); - $this->organization = $this->get('organization'); + $raw['autonomous_system_organization'] ?? null; + $this->isp = $raw['isp'] ?? null; + $this->mobileCountryCode = $raw['mobile_country_code'] ?? null; + $this->mobileNetworkCode = $raw['mobile_network_code'] ?? null; + $this->organization = $raw['organization'] ?? null; - $ipAddress = $this->get('ip_address'); + $ipAddress = $raw['ip_address']; $this->ipAddress = $ipAddress; - $this->network = Util::cidr($ipAddress, $this->get('prefix_len')); + $this->network = Util::cidr($ipAddress, $raw['prefix_len']); + } + + public function jsonSerialize(): ?array + { + $js = []; + if ($this->autonomousSystemNumber !== null) { + $js['autonomous_system_number'] = $this->autonomousSystemNumber; + } + if ($this->autonomousSystemOrganization !== null) { + $js['autonomous_system_organization'] = $this->autonomousSystemOrganization; + } + if ($this->isp !== null) { + $js['isp'] = $this->isp; + } + if ($this->mobileCountryCode !== null) { + $js['mobile_country_code'] = $this->mobileCountryCode; + } + if ($this->mobileNetworkCode !== null) { + $js['mobile_network_code'] = $this->mobileNetworkCode; + } + if ($this->organization !== null) { + $js['organization'] = $this->organization; + } + $js['ip_address'] = $this->ipAddress; + $js['network'] = $this->network; + + return $js; } } diff --git a/lib/maxmind/GeoIp2/README.md b/lib/maxmind/GeoIp2/README.md new file mode 100644 index 00000000000..01502e9bd0c --- /dev/null +++ b/lib/maxmind/GeoIp2/README.md @@ -0,0 +1,453 @@ +# GeoIP2 PHP API # + +## Description ## + +This package provides an API for the GeoIP2 and GeoLite2 +[web services](https://dev.maxmind.com/geoip/docs/web-services?lang=en) and +[databases](https://dev.maxmind.com/geoip/docs/databases?lang=en). + +## Install via Composer ## + +We recommend installing this package with [Composer](https://getcomposer.org/). + +### Download Composer ### + +To download Composer, run in the root directory of your project: + +```bash +curl -sS https://getcomposer.org/installer | php +``` + +You should now have the file `composer.phar` in your project directory. + +### Install Dependencies ### + +Run in your project root: + +```sh +php composer.phar require geoip2/geoip2:~2.0 +``` + +You should now have the files `composer.json` and `composer.lock` as well as +the directory `vendor` in your project directory. If you use a version control +system, `composer.json` should be added to it. + +### Require Autoloader ### + +After installing the dependencies, you need to require the Composer autoloader +from your code: + +```php +require 'vendor/autoload.php'; +``` + +## Install via Phar ## + +Although we strongly recommend using Composer, we also provide a +[phar archive](https://php.net/manual/en/book.phar.php) containing most of the +dependencies for GeoIP2. Our latest phar archive is available on +[our releases page](https://github.com/maxmind/GeoIP2-php/releases). + +### Install Dependencies ### + +In order to use the phar archive, you must have the PHP +[Phar extension](https://php.net/manual/en/book.phar.php) installed and +enabled. + +If you will be making web service requests, you must have the PHP +[cURL extension](https://php.net/manual/en/book.curl.php) +installed to use this archive. For Debian based distributions, this can +typically be found in the the `php-curl` package. For other operating +systems, please consult the relevant documentation. After installing the +extension you may need to restart your web server. + +If you are missing this extension, you will see errors like the following: + +``` +PHP Fatal error: Uncaught Error: Call to undefined function MaxMind\WebService\curl_version() +``` + +### Require Package ### + +To use the archive, just require it from your script: + +```php +require 'geoip2.phar'; +``` + +## Optional C Extension ## + +The [MaxMind DB API](https://github.com/maxmind/MaxMind-DB-Reader-php) +includes an optional C extension that you may install to dramatically increase +the performance of lookups in GeoIP2 or GeoLite2 databases. To install, please +follow the instructions included with that API. + +The extension has no effect on web-service lookups. + +## IP Geolocation Usage ## + +IP geolocation is inherently imprecise. Locations are often near the center of +the population. Any location provided by a GeoIP2 database or web service +should not be used to identify a particular address or household. + +## Database Reader ## + +### Usage ### + +To use this API, you must create a new `\GeoIp2\Database\Reader` object with +the path to the database file as the first argument to the constructor. You +may then call the method corresponding to the database you are using. + +If the lookup succeeds, the method call will return a model class for the +record in the database. This model in turn contains multiple container +classes for the different parts of the data such as the city in which the +IP address is located. + +If the record is not found, a `\GeoIp2\Exception\AddressNotFoundException` +is thrown. If the database is invalid or corrupt, a +`\MaxMind\Db\InvalidDatabaseException` will be thrown. + +See the [API documentation](https://maxmind.github.io/GeoIP2-php/) for more +details. + +### City Example ### + +```php +city('128.101.101.101'); + +print($record->country->isoCode . "\n"); // 'US' +print($record->country->name . "\n"); // 'United States' +print($record->country->names['zh-CN'] . "\n"); // '美国' + +print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' +print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' + +print($record->city->name . "\n"); // 'Minneapolis' + +print($record->postal->code . "\n"); // '55455' + +print($record->location->latitude . "\n"); // 44.9733 +print($record->location->longitude . "\n"); // -93.2323 + +print($record->traits->network . "\n"); // '128.101.101.101/32' + +``` + +### Anonymous IP Example ### + +```php +anonymousIp('128.101.101.101'); + +if ($record->isAnonymous) { print "anon\n"; } +print($record->ipAddress . "\n"); // '128.101.101.101' +print($record->network . "\n"); // '128.101.101.101/32' + +``` + +### Connection-Type Example ### + +```php +connectionType('128.101.101.101'); + +print($record->connectionType . "\n"); // 'Corporate' +print($record->ipAddress . "\n"); // '128.101.101.101' +print($record->network . "\n"); // '128.101.101.101/32' + +``` + +### Domain Example ### + +```php +domain('128.101.101.101'); + +print($record->domain . "\n"); // 'umn.edu' +print($record->ipAddress . "\n"); // '128.101.101.101' +print($record->network . "\n"); // '128.101.101.101/32' + +``` + +### Enterprise Example ### + +```php +enterprise method to do a lookup in the Enterprise database +$record = $enterpriseDbReader->enterprise('128.101.101.101'); + +print($record->country->confidence . "\n"); // 99 +print($record->country->isoCode . "\n"); // 'US' +print($record->country->name . "\n"); // 'United States' +print($record->country->names['zh-CN'] . "\n"); // '美国' + +print($record->mostSpecificSubdivision->confidence . "\n"); // 77 +print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' +print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' + +print($record->city->confidence . "\n"); // 60 +print($record->city->name . "\n"); // 'Minneapolis' + +print($record->postal->code . "\n"); // '55455' + +print($record->location->accuracyRadius . "\n"); // 50 +print($record->location->latitude . "\n"); // 44.9733 +print($record->location->longitude . "\n"); // -93.2323 + +print($record->traits->network . "\n"); // '128.101.101.101/32' + +``` + +### ISP Example ### + +```php +isp('128.101.101.101'); + +print($record->autonomousSystemNumber . "\n"); // 217 +print($record->autonomousSystemOrganization . "\n"); // 'University of Minnesota' +print($record->isp . "\n"); // 'University of Minnesota' +print($record->organization . "\n"); // 'University of Minnesota' + +print($record->ipAddress . "\n"); // '128.101.101.101' +print($record->network . "\n"); // '128.101.101.101/32' + +``` + +## Database Updates ## + +You can keep your databases up to date with our +[GeoIP Update program](https://github.com/maxmind/geoipupdate/releases). +[Learn more about GeoIP Update on our developer +portal.](https://dev.maxmind.com/geoip/updating-databases?lang=en) + +There is also a third-party tool for updating databases using PHP and +Composer. MaxMind does not offer support for this tool or maintain it. +[Learn more about the Geoip2 Update tool for PHP and Composer on its +GitHub page.](https://github.com/tronovav/geoip2-update) + +## Web Service Client ## + +### Usage ### + +To use this API, you must create a new `\GeoIp2\WebService\Client` +object with your `$accountId` and `$licenseKey`: + +```php +$client = new Client(42, 'abcdef123456'); +``` + +You may also call the constructor with additional arguments. The third argument +specifies the language preferences when using the `->name` method on the model +classes that this client creates. The fourth argument is additional options +such as `host` and `timeout`. + +For instance, to call the GeoLite2 web service instead of the GeoIP2 web +service: + +```php +$client = new Client(42, 'abcdef123456', ['en'], ['host' => 'geolite.info']); +``` + +To call the Sandbox GeoIP2 web service instead of the production GeoIP2 web +service: + +```php +$client = new Client(42, 'abcdef123456', ['en'], ['host' => 'sandbox.maxmind.com']); +``` + +After creating the client, you may now call the method corresponding to a +specific endpoint with the IP address to look up, e.g.: + +```php +$record = $client->city('128.101.101.101'); +``` + +If the request succeeds, the method call will return a model class for the +endpoint you called. This model in turn contains multiple record classes, each +of which represents part of the data returned by the web service. + +If there is an error, a structured exception is thrown. + +See the [API documentation](https://maxmind.github.io/GeoIP2-php/) for more +details. + +### Example ### + +```php +city('128.101.101.101'); + +print($record->country->isoCode . "\n"); // 'US' +print($record->country->name . "\n"); // 'United States' +print($record->country->names['zh-CN'] . "\n"); // '美国' + +print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' +print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' + +print($record->city->name . "\n"); // 'Minneapolis' + +print($record->postal->code . "\n"); // '55455' + +print($record->location->latitude . "\n"); // 44.9733 +print($record->location->longitude . "\n"); // -93.2323 + +print($record->traits->network . "\n"); // '128.101.101.101/32' + +``` + +## Values to use for Database or Array Keys ## + +**We strongly discourage you from using a value from any `names` property as +a key in a database or array.** + +These names may change between releases. Instead we recommend using one of the +following: + +* `GeoIp2\Record\City` - `$city->geonameId` +* `GeoIp2\Record\Continent` - `$continent->code` or `$continent->geonameId` +* `GeoIp2\Record\Country` and `GeoIp2\Record\RepresentedCountry` - + `$country->isoCode` or `$country->geonameId` +* `GeoIp2\Record\Subdivision` - `$subdivision->isoCode` or `$subdivision->geonameId` + +### What data is returned? ### + +While many of the end points return the same basic records, the attributes +which can be populated vary between end points. In addition, while an end +point may offer a particular piece of data, MaxMind does not always have every +piece of data for any given IP address. + +Because of these factors, it is possible for any end point to return a record +where some or all of the attributes are unpopulated. + +See the +[GeoIP2 web service docs](https://dev.maxmind.com/geoip/docs/web-services?lang=en) +for details on what data each end point may return. + +The only piece of data which is always returned is the `ipAddress` +attribute in the `GeoIp2\Record\Traits` record. + +## Integration with GeoNames ## + +[GeoNames](https://www.geonames.org/) offers web services and downloadable +databases with data on geographical features around the world, including +populated places. They offer both free and paid premium data. Each +feature is unique identified by a `geonameId`, which is an integer. + +Many of the records returned by the GeoIP2 web services and databases +include a `geonameId` property. This is the ID of a geographical feature +(city, region, country, etc.) in the GeoNames database. + +Some of the data that MaxMind provides is also sourced from GeoNames. We +source things like place names, ISO codes, and other similar data from +the GeoNames premium data set. + +## Reporting data problems ## + +If the problem you find is that an IP address is incorrectly mapped, +please +[submit your correction to MaxMind](https://www.maxmind.com/en/correction). + +If you find some other sort of mistake, like an incorrect spelling, +please check the [GeoNames site](https://www.geonames.org/) first. Once +you've searched for a place and found it on the GeoNames map view, there +are a number of links you can use to correct data ("move", "edit", +"alternate names", etc.). Once the correction is part of the GeoNames +data set, it will be automatically incorporated into future MaxMind +releases. + +If you are a paying MaxMind customer and you're not sure where to submit +a correction, please +[contact MaxMind support](https://www.maxmind.com/en/support) for help. + +## Other Support ## + +Please report all issues with this code using the +[GitHub issue tracker](https://github.com/maxmind/GeoIP2-php/issues). + +If you are having an issue with a MaxMind service that is not specific +to the client API, please see +[our support page](https://www.maxmind.com/en/support). + +## Requirements ## + +This library requires PHP 8.1 or greater. + +This library also relies on the [MaxMind DB Reader](https://github.com/maxmind/MaxMind-DB-Reader-php). + +## Contributing ## + +Patches and pull requests are encouraged. All code should follow the PSR-2 +style guidelines. Please include unit tests whenever possible. You may obtain +the test data for the maxmind-db folder by running `git submodule update +--init --recursive` or adding `--recursive` to your initial clone, or from +https://github.com/maxmind/MaxMind-DB + +## Versioning ## + +The GeoIP2 PHP API uses [Semantic Versioning](https://semver.org/). + +## Copyright and License ## + +This software is Copyright (c) 2013-2023 by MaxMind, Inc. + +This is free software, licensed under the Apache License, Version 2.0. diff --git a/lib/maxmind/GeoIp2/Record/AbstractNamedRecord.php b/lib/maxmind/GeoIp2/Record/AbstractNamedRecord.php new file mode 100644 index 00000000000..414841d5df3 --- /dev/null +++ b/lib/maxmind/GeoIp2/Record/AbstractNamedRecord.php @@ -0,0 +1,49 @@ +names = $record['names'] ?? []; + + foreach ($locales as $locale) { + if (isset($this->names[$locale])) { + $this->name = $this->names[$locale]; + + return; + } + } + $this->name = null; + } + + public function jsonSerialize(): array + { + $js = []; + if (!empty($this->names)) { + $js['names'] = $this->names; + } + + return $js; + } +} diff --git a/lib/maxmind/GeoIp2/Record/AbstractPlaceRecord.php b/lib/maxmind/GeoIp2/Record/AbstractPlaceRecord.php index 684ab8c8cca..364d7bb810b 100644 --- a/lib/maxmind/GeoIp2/Record/AbstractPlaceRecord.php +++ b/lib/maxmind/GeoIp2/Record/AbstractPlaceRecord.php @@ -4,64 +4,43 @@ declare(strict_types=1); namespace GeoIp2\Record; -abstract class AbstractPlaceRecord extends AbstractRecord +abstract class AbstractPlaceRecord extends AbstractNamedRecord { /** - * @var array + * @var int|null A value from 0-100 indicating MaxMind's + * confidence that the location level is correct. This attribute is only available + * from the Insights service and the GeoIP2 Enterprise database. */ - private $locales; + public readonly ?int $confidence; + + /** + * @var int|null The GeoName ID for the location level. This attribute + * is returned by all location services and databases. + */ + public readonly ?int $geonameId; /** * @ignore */ - public function __construct(?array $record, array $locales = ['en']) + public function __construct(array $record, array $locales = ['en']) { - $this->locales = $locales; - parent::__construct($record); + parent::__construct($record, $locales); + + $this->confidence = $record['confidence'] ?? null; + $this->geonameId = $record['geoname_id'] ?? null; } - /** - * @ignore - * - * @return mixed - */ - public function __get(string $attr) + public function jsonSerialize(): array { - if ($attr === 'name') { - return $this->name(); + $js = parent::jsonSerialize(); + if ($this->confidence !== null) { + $js['confidence'] = $this->confidence; } - return parent::__get($attr); - } - - /** - * @ignore - */ - public function __isset(string $attr): bool - { - if ($attr === 'name') { - return $this->firstSetNameLocale() !== null; + if ($this->geonameId !== null) { + $js['geoname_id'] = $this->geonameId; } - return parent::__isset($attr); - } - - private function name(): ?string - { - $locale = $this->firstSetNameLocale(); - - // @phpstan-ignore-next-line - return $locale === null ? null : $this->names[$locale]; - } - - private function firstSetNameLocale(): ?string - { - foreach ($this->locales as $locale) { - if (isset($this->names[$locale])) { - return $locale; - } - } - - return null; + return $js; } } diff --git a/lib/maxmind/GeoIp2/Record/AbstractRecord.php b/lib/maxmind/GeoIp2/Record/AbstractRecord.php deleted file mode 100644 index 5ddb3c67929..00000000000 --- a/lib/maxmind/GeoIp2/Record/AbstractRecord.php +++ /dev/null @@ -1,67 +0,0 @@ - - */ - private $record; - - /** - * @ignore - */ - public function __construct(?array $record) - { - $this->record = isset($record) ? $record : []; - } - - /** - * @ignore - * - * @return mixed - */ - public function __get(string $attr) - { - // XXX - kind of ugly but greatly reduces boilerplate code - $key = $this->attributeToKey($attr); - - if ($this->__isset($attr)) { - return $this->record[$key]; - } - if ($this->validAttribute($attr)) { - if (preg_match('/^is_/', $key)) { - return false; - } - - return null; - } - - throw new \RuntimeException("Unknown attribute: $attr"); - } - - public function __isset(string $attr): bool - { - return $this->validAttribute($attr) - && isset($this->record[$this->attributeToKey($attr)]); - } - - private function attributeToKey(string $attr): string - { - return strtolower(preg_replace('/([A-Z])/', '_\1', $attr)); - } - - private function validAttribute(string $attr): bool - { - // @phpstan-ignore-next-line - return \in_array($attr, $this->validAttributes, true); - } - - public function jsonSerialize(): ?array - { - return $this->record; - } -} diff --git a/lib/maxmind/GeoIp2/Record/City.php b/lib/maxmind/GeoIp2/Record/City.php index f25dcb389e1..9ebb9a28b13 100644 --- a/lib/maxmind/GeoIp2/Record/City.php +++ b/lib/maxmind/GeoIp2/Record/City.php @@ -9,25 +9,6 @@ namespace GeoIp2\Record; * * This record is returned by all location services and databases besides * Country. - * - * @property-read int|null $confidence A value from 0-100 indicating MaxMind's - * confidence that the city is correct. This attribute is only available - * from the Insights service and the GeoIP2 Enterprise database. - * @property-read int|null $geonameId The GeoName ID for the city. This attribute - * is returned by all location services and databases. - * @property-read string|null $name The name of the city based on the locales list - * passed to the constructor. This attribute is returned by all location - * services and databases. - * @property-read array|null $names An array map where the keys are locale codes - * and the values are names. This attribute is returned by all location - * services and databases. */ -class City extends AbstractPlaceRecord -{ - /** - * @ignore - * - * @var array - */ - protected $validAttributes = ['confidence', 'geonameId', 'names']; -} +// phpcs:disable +class City extends AbstractPlaceRecord {} diff --git a/lib/maxmind/GeoIp2/Record/Continent.php b/lib/maxmind/GeoIp2/Record/Continent.php index 103e2e3d840..873014ae2b5 100644 --- a/lib/maxmind/GeoIp2/Record/Continent.php +++ b/lib/maxmind/GeoIp2/Record/Continent.php @@ -8,29 +8,43 @@ namespace GeoIp2\Record; * Contains data for the continent record associated with an IP address. * * This record is returned by all location services and databases. - * - * @property-read string|null $code A two character continent code like "NA" (North - * America) or "OC" (Oceania). This attribute is returned by all location - * services and databases. - * @property-read int|null $geonameId The GeoName ID for the continent. This - * attribute is returned by all location services and databases. - * @property-read string|null $name Returns the name of the continent based on the - * locales list passed to the constructor. This attribute is returned by all location - * services and databases. - * @property-read array|null $names An array map where the keys are locale codes - * and the values are names. This attribute is returned by all location - * services and databases. */ -class Continent extends AbstractPlaceRecord +class Continent extends AbstractNamedRecord { /** - * @ignore - * - * @var array + * @var string|null A two character continent code like "NA" (North + * America) or "OC" (Oceania). This attribute is returned by all location + * services and databases. */ - protected $validAttributes = [ - 'code', - 'geonameId', - 'names', - ]; + public readonly ?string $code; + + /** + * @var int|null The GeoName ID for the continent. This + * attribute is returned by all location services and databases. + */ + public readonly ?int $geonameId; + + /** + * @ignore + */ + public function __construct(array $record, array $locales = ['en']) + { + parent::__construct($record, $locales); + + $this->code = $record['code'] ?? null; + $this->geonameId = $record['geoname_id'] ?? null; + } + + public function jsonSerialize(): array + { + $js = parent::jsonSerialize(); + if ($this->code !== null) { + $js['code'] = $this->code; + } + if ($this->geonameId !== null) { + $js['geoname_id'] = $this->geonameId; + } + + return $js; + } } diff --git a/lib/maxmind/GeoIp2/Record/Country.php b/lib/maxmind/GeoIp2/Record/Country.php index 3009ebc622b..438ebd89cd8 100644 --- a/lib/maxmind/GeoIp2/Record/Country.php +++ b/lib/maxmind/GeoIp2/Record/Country.php @@ -8,37 +8,44 @@ namespace GeoIp2\Record; * Contains data for the country record associated with an IP address. * * This record is returned by all location services and databases. - * - * @property-read int|null $confidence A value from 0-100 indicating MaxMind's - * confidence that the country is correct. This attribute is only available - * from the Insights service and the GeoIP2 Enterprise database. - * @property-read int|null $geonameId The GeoName ID for the country. This - * attribute is returned by all location services and databases. - * @property-read bool $isInEuropeanUnion This is true if the country is a - * member state of the European Union. This attribute is returned by all - * location services and databases. - * @property-read string|null $isoCode The two-character ISO 3166-1 alpha code - * for the country. See https://en.wikipedia.org/wiki/ISO_3166-1. This - * attribute is returned by all location services and databases. - * @property-read string|null $name The name of the country based on the locales - * list passed to the constructor. This attribute is returned by all location - * services and databases. - * @property-read array|null $names An array map where the keys are locale codes - * and the values are names. This attribute is returned by all location - * services and databases. */ class Country extends AbstractPlaceRecord { /** - * @ignore - * - * @var array + * @var bool This is true if the country is a + * member state of the European Union. This attribute is returned by all + * location services and databases. */ - protected $validAttributes = [ - 'confidence', - 'geonameId', - 'isInEuropeanUnion', - 'isoCode', - 'names', - ]; + public readonly bool $isInEuropeanUnion; + + /** + * @var string|null The two-character ISO 3166-1 alpha code + * for the country. See https://en.wikipedia.org/wiki/ISO_3166-1. This + * attribute is returned by all location services and databases. + */ + public readonly ?string $isoCode; + + /** + * @ignore + */ + public function __construct(array $record, array $locales = ['en']) + { + parent::__construct($record, $locales); + + $this->isInEuropeanUnion = $record['is_in_european_union'] ?? false; + $this->isoCode = $record['iso_code'] ?? null; + } + + public function jsonSerialize(): array + { + $js = parent::jsonSerialize(); + if ($this->isInEuropeanUnion !== false) { + $js['is_in_european_union'] = $this->isInEuropeanUnion; + } + if ($this->isoCode !== null) { + $js['iso_code'] = $this->isoCode; + } + + return $js; + } } diff --git a/lib/maxmind/GeoIp2/Record/Location.php b/lib/maxmind/GeoIp2/Record/Location.php index cb6111c2c44..ca13b52c1e3 100644 --- a/lib/maxmind/GeoIp2/Record/Location.php +++ b/lib/maxmind/GeoIp2/Record/Location.php @@ -9,48 +9,97 @@ namespace GeoIp2\Record; * * This record is returned by all location services and databases besides * Country. - * - * @property-read int|null $averageIncome The average income in US dollars - * associated with the requested IP address. This attribute is only available - * from the Insights service. - * @property-read int|null $accuracyRadius The approximate accuracy radius in - * kilometers around the latitude and longitude for the IP address. This is - * the radius where we have a 67% confidence that the device using the IP - * address resides within the circle centered at the latitude and longitude - * with the provided radius. - * @property-read float|null $latitude The approximate latitude of the location - * associated with the IP address. This value is not precise and should not be - * used to identify a particular address or household. - * @property-read float|null $longitude The approximate longitude of the location - * associated with the IP address. This value is not precise and should not be - * used to identify a particular address or household. - * @property-read int|null $populationDensity The estimated population per square - * kilometer associated with the IP address. This attribute is only available - * from the Insights service. - * @property-read int|null $metroCode The metro code of the location if the location - * is in the US. MaxMind returns the same metro codes as the - * Google AdWords API. See - * https://developers.google.com/adwords/api/docs/appendix/cities-DMAregions. - * @property-read string|null $timeZone The time zone associated with location, as - * specified by the IANA Time Zone Database, e.g., "America/New_York". See - * https://www.iana.org/time-zones. */ -class Location extends AbstractRecord +class Location implements \JsonSerializable { /** - * @ignore - * - * @var array + * @var int|null The average income in US dollars + * associated with the requested IP address. This attribute is only available + * from the Insights service. */ - protected $validAttributes = [ - 'averageIncome', - 'accuracyRadius', - 'latitude', - 'longitude', - 'metroCode', - 'populationDensity', - 'postalCode', - 'postalConfidence', - 'timeZone', - ]; + public readonly ?int $averageIncome; + + /** + * @var int|null The approximate accuracy radius in + * kilometers around the latitude and longitude for the IP address. This is + * the radius where we have a 67% confidence that the device using the IP + * address resides within the circle centered at the latitude and longitude + * with the provided radius. + */ + public readonly ?int $accuracyRadius; + + /** + * @var float|null The approximate latitude of the location + * associated with the IP address. This value is not precise and should not be + * used to identify a particular address or household. + */ + public readonly ?float $latitude; + + /** + * @var float|null The approximate longitude of the location + * associated with the IP address. This value is not precise and should not be + * used to identify a particular address or household. + */ + public readonly ?float $longitude; + + /** + * @var int|null The metro code of the location if the location + * is in the US. MaxMind returns the same metro codes as the + * Google AdWords API. See + * https://developers.google.com/adwords/api/docs/appendix/cities-DMAregions. + */ + public readonly ?int $metroCode; + + /** + * @var int|null The estimated population per square + * kilometer associated with the IP address. This attribute is only available + * from the Insights service. + */ + public readonly ?int $populationDensity; + + /** + * @var string|null The time zone associated with location, as + * specified by the IANA Time Zone Database, e.g., "America/New_York". See + * https://www.iana.org/time-zones. + */ + public readonly ?string $timeZone; + + public function __construct(array $record) + { + $this->averageIncome = $record['average_income'] ?? null; + $this->accuracyRadius = $record['accuracy_radius'] ?? null; + $this->latitude = $record['latitude'] ?? null; + $this->longitude = $record['longitude'] ?? null; + $this->metroCode = $record['metro_code'] ?? null; + $this->populationDensity = $record['population_density'] ?? null; + $this->timeZone = $record['time_zone'] ?? null; + } + + public function jsonSerialize(): array + { + $js = []; + if ($this->averageIncome !== null) { + $js['average_income'] = $this->averageIncome; + } + if ($this->accuracyRadius !== null) { + $js['accuracy_radius'] = $this->accuracyRadius; + } + if ($this->latitude !== null) { + $js['latitude'] = $this->latitude; + } + if ($this->longitude !== null) { + $js['longitude'] = $this->longitude; + } + if ($this->metroCode !== null) { + $js['metro_code'] = $this->metroCode; + } + if ($this->populationDensity !== null) { + $js['population_density'] = $this->populationDensity; + } + if ($this->timeZone !== null) { + $js['time_zone'] = $this->timeZone; + } + + return $js; + } } diff --git a/lib/maxmind/GeoIp2/Record/MaxMind.php b/lib/maxmind/GeoIp2/Record/MaxMind.php index e972506e568..e80334d5846 100644 --- a/lib/maxmind/GeoIp2/Record/MaxMind.php +++ b/lib/maxmind/GeoIp2/Record/MaxMind.php @@ -8,16 +8,27 @@ namespace GeoIp2\Record; * Contains data about your account. * * This record is returned by all location services and databases. - * - * @property-read int|null $queriesRemaining The number of remaining queries you - * have for the service you are calling. */ -class MaxMind extends AbstractRecord +class MaxMind implements \JsonSerializable { /** - * @ignore - * - * @var array + * @var int|null the number of remaining queries you + * have for the service you are calling */ - protected $validAttributes = ['queriesRemaining']; + public readonly ?int $queriesRemaining; + + public function __construct(array $record) + { + $this->queriesRemaining = $record['queries_remaining'] ?? null; + } + + public function jsonSerialize(): array + { + $js = []; + if ($this->queriesRemaining !== null) { + $js['queries_remaining'] = $this->queriesRemaining; + } + + return $js; + } } diff --git a/lib/maxmind/GeoIp2/Record/Postal.php b/lib/maxmind/GeoIp2/Record/Postal.php index 3e9c2377b88..d3adac6f293 100644 --- a/lib/maxmind/GeoIp2/Record/Postal.php +++ b/lib/maxmind/GeoIp2/Record/Postal.php @@ -9,22 +9,44 @@ namespace GeoIp2\Record; * * This record is returned by all location databases and services besides * Country. - * - * @property-read string|null $code The postal code of the location. Postal codes - * are not available for all countries. In some countries, this will only - * contain part of the postal code. This attribute is returned by all location - * databases and services besides Country. - * @property-read int|null $confidence A value from 0-100 indicating MaxMind's - * confidence that the postal code is correct. This attribute is only - * available from the Insights service and the GeoIP2 Enterprise - * database. */ -class Postal extends AbstractRecord +class Postal implements \JsonSerializable { /** - * @ignore - * - * @var array + * @var string|null The postal code of the location. Postal codes + * are not available for all countries. In some countries, this will only + * contain part of the postal code. This attribute is returned by all location + * databases and services besides Country. */ - protected $validAttributes = ['code', 'confidence']; + public readonly ?string $code; + + /** + * @var int|null A value from 0-100 indicating MaxMind's + * confidence that the postal code is correct. This attribute is only + * available from the Insights service and the GeoIP2 Enterprise + * database. + */ + public readonly ?int $confidence; + + /** + * @ignore + */ + public function __construct(array $record) + { + $this->code = $record['code'] ?? null; + $this->confidence = $record['confidence'] ?? null; + } + + public function jsonSerialize(): array + { + $js = []; + if ($this->code !== null) { + $js['code'] = $this->code; + } + if ($this->confidence !== null) { + $js['confidence'] = $this->confidence; + } + + return $js; + } } diff --git a/lib/maxmind/GeoIp2/Record/RepresentedCountry.php b/lib/maxmind/GeoIp2/Record/RepresentedCountry.php index 727c034afa2..a810fc633a0 100644 --- a/lib/maxmind/GeoIp2/Record/RepresentedCountry.php +++ b/lib/maxmind/GeoIp2/Record/RepresentedCountry.php @@ -10,24 +10,33 @@ namespace GeoIp2\Record; * This class contains the country-level data associated with an IP address * for the IP's represented country. The represented country is the country * represented by something like a military base. - * - * @property-read string|null $type A string indicating the type of entity that is - * representing the country. Currently we only return military - * but this could expand to include other types in the future. */ class RepresentedCountry extends Country { /** - * @ignore - * - * @var array + * @var string|null A string indicating the type of entity that is + * representing the country. Currently we only return military + * but this could expand to include other types in the future. */ - protected $validAttributes = [ - 'confidence', - 'geonameId', - 'isInEuropeanUnion', - 'isoCode', - 'names', - 'type', - ]; + public readonly ?string $type; + + /** + * @ignore + */ + public function __construct(array $record, array $locales = ['en']) + { + parent::__construct($record, $locales); + + $this->type = $record['type'] ?? null; + } + + public function jsonSerialize(): array + { + $js = parent::jsonSerialize(); + if ($this->type !== null) { + $js['type'] = $this->type; + } + + return $js; + } } diff --git a/lib/maxmind/GeoIp2/Record/Subdivision.php b/lib/maxmind/GeoIp2/Record/Subdivision.php index 0e83549382a..cd11e58bb54 100644 --- a/lib/maxmind/GeoIp2/Record/Subdivision.php +++ b/lib/maxmind/GeoIp2/Record/Subdivision.php @@ -9,36 +9,34 @@ namespace GeoIp2\Record; * * This record is returned by all location databases and services besides * Country. - * - * @property-read int|null $confidence This is a value from 0-100 indicating - * MaxMind's confidence that the subdivision is correct. This attribute is - * only available from the Insights service and the GeoIP2 Enterprise - * database. - * @property-read int|null $geonameId This is a GeoName ID for the subdivision. - * This attribute is returned by all location databases and services besides - * Country. - * @property-read string|null $isoCode This is a string up to three characters long - * contain the subdivision portion of the ISO 3166-2 code. See - * https://en.wikipedia.org/wiki/ISO_3166-2. This attribute is returned by all - * location databases and services except Country. - * @property-read string|null $name The name of the subdivision based on the - * locales list passed to the constructor. This attribute is returned by all - * location databases and services besides Country. - * @property-read array|null $names An array map where the keys are locale codes - * and the values are names. This attribute is returned by all location - * databases and services besides Country. */ class Subdivision extends AbstractPlaceRecord { /** - * @ignore - * - * @var array + * @var string|null This is a string up to three characters long + * contain the subdivision portion of the ISO 3166-2 code. See + * https://en.wikipedia.org/wiki/ISO_3166-2. This attribute is returned by all + * location databases and services except Country. */ - protected $validAttributes = [ - 'confidence', - 'geonameId', - 'isoCode', - 'names', - ]; + public readonly ?string $isoCode; + + /** + * @ignore + */ + public function __construct(array $record, array $locales = ['en']) + { + parent::__construct($record, $locales); + + $this->isoCode = $record['iso_code'] ?? null; + } + + public function jsonSerialize(): array + { + $js = parent::jsonSerialize(); + if ($this->isoCode !== null) { + $js['iso_code'] = $this->isoCode; + } + + return $js; + } } diff --git a/lib/maxmind/GeoIp2/Record/Traits.php b/lib/maxmind/GeoIp2/Record/Traits.php index 8000d5042e3..493f86ae0b1 100644 --- a/lib/maxmind/GeoIp2/Record/Traits.php +++ b/lib/maxmind/GeoIp2/Record/Traits.php @@ -10,149 +10,289 @@ use GeoIp2\Util; * Contains data for the traits record associated with an IP address. * * This record is returned by all location services and databases. - * - * @property-read int|null $autonomousSystemNumber The autonomous system number - * associated with the IP address. See - * https://en.wikipedia.org/wiki/Autonomous_system_(Internet%29. This attribute - * is only available from the City Plus and Insights web services and the - * GeoIP2 Enterprise database. - * @property-read string|null $autonomousSystemOrganization The organization - * associated with the registered autonomous system number for the IP address. - * See https://en.wikipedia.org/wiki/Autonomous_system_(Internet%29. This - * attribute is only available from the City Plus and Insights web services and - * the GeoIP2 Enterprise database. - * @property-read string|null $connectionType The connection type may take the - * following values: "Dialup", "Cable/DSL", "Corporate", "Cellular". - * Additional values may be added in the future. This attribute is only - * available in the GeoIP2 Enterprise database. - * @property-read string|null $domain The second level domain associated with the - * IP address. This will be something like "example.com" or "example.co.uk", - * not "foo.example.com". This attribute is only available from the - * City Plus and Insights web services and the GeoIP2 Enterprise - * database. - * @property-read string $ipAddress The IP address that the data in the model - * is for. If you performed a "me" lookup against the web service, this - * will be the externally routable IP address for the system the code is - * running on. If the system is behind a NAT, this may differ from the IP - * address locally assigned to it. This attribute is returned by all end - * points. - * @property-read bool $isAnonymous This is true if the IP address belongs to - * any sort of anonymous network. This property is only available from GeoIP2 - * Insights. - * @property-read bool $isAnonymousProxy *Deprecated.* Please see our GeoIP2 - * Anonymous IP database - * (https://www.maxmind.com/en/geoip2-anonymous-ip-database) to determine - * whether the IP address is used by an anonymizing service. - * @property-read bool $isAnonymousVpn This is true if the IP address is - * registered to an anonymous VPN provider. If a VPN provider does not register - * subnets under names associated with them, we will likely only flag their IP - * ranges using the isHostingProvider property. This property is only available - * from GeoIP2 Insights. - * @property-read bool $isHostingProvider This is true if the IP address belongs - * to a hosting or VPN provider (see description of isAnonymousVpn property). - * This property is only available from GeoIP2 Insights. - * @property-read bool $isLegitimateProxy This attribute is true if MaxMind - * believes this IP address to be a legitimate proxy, such as an internal - * VPN used by a corporation. This attribute is only available in the GeoIP2 - * Enterprise database. - * @property-read bool $isPublicProxy This is true if the IP address belongs to - * a public proxy. This property is only available from GeoIP2 Insights. - * @property-read bool $isResidentialProxy This is true if the IP address is - * on a suspected anonymizing network and belongs to a residential ISP. This - * property is only available from GeoIP2 Insights. - * @property-read bool $isSatelliteProvider *Deprecated.* Due to the - * increased coverage by mobile carriers, very few satellite providers now - * serve multiple countries. As a result, the output does not provide - * sufficiently relevant data for us to maintain it. - * @property-read bool $isTorExitNode This is true if the IP address is a Tor - * exit node. This property is only available from GeoIP2 Insights. - * @property-read string|null $isp The name of the ISP associated with the IP - * address. This attribute is only available from the City Plus and Insights - * web services and the GeoIP2 Enterprise database. - * @property-read string $network The network in CIDR notation associated with - * the record. In particular, this is the largest network where all of the - * fields besides $ipAddress have the same value. - * @property-read string|null $organization The name of the organization - * associated with the IP address. This attribute is only available from the - * City Plus and Insights web services and the GeoIP2 Enterprise database. - * @property-read string|null $mobileCountryCode The [mobile country code - * (MCC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with - * the IP address and ISP. This property is available from the City Plus and - * Insights web services and the GeoIP2 Enterprise database. - * @property-read string|null $mobileNetworkCode The [mobile network code - * (MNC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with - * the IP address and ISP. This property is available from the City Plus and - * Insights web services and the GeoIP2 Enterprise database. - * @property-read float|null $staticIpScore An indicator of how static or - * dynamic an IP address is. This property is only available from GeoIP2 - * Insights. - * @property-read int|null $userCount The estimated number of users sharing - * the IP/network during the past 24 hours. For IPv4, the count is for the - * individual IP. For IPv6, the count is for the /64 network. This property is - * only available from GeoIP2 Insights. - * @property-read string|null $userType

The user type associated with the IP - * address. This can be one of the following values:

- *
    - *
  • business - *
  • cafe - *
  • cellular - *
  • college - *
  • consumer_privacy_network - *
  • content_delivery_network - *
  • dialup - *
  • government - *
  • hosting - *
  • library - *
  • military - *
  • residential - *
  • router - *
  • school - *
  • search_engine_spider - *
  • traveler - *
- *

- * This attribute is only available from the Insights web service and the - * GeoIP2 Enterprise database. - *

*/ -class Traits extends AbstractRecord +class Traits implements \JsonSerializable { /** - * @ignore - * - * @var array + * @var int|null The autonomous system number + * associated with the IP address. See + * https://en.wikipedia.org/wiki/Autonomous_system_(Internet%29. This attribute + * is only available from the City Plus and Insights web services and the + * GeoIP2 Enterprise database. */ - protected $validAttributes = [ - 'autonomousSystemNumber', - 'autonomousSystemOrganization', - 'connectionType', - 'domain', - 'ipAddress', - 'isAnonymous', - 'isAnonymousProxy', - 'isAnonymousVpn', - 'isHostingProvider', - 'isLegitimateProxy', - 'isp', - 'isPublicProxy', - 'isResidentialProxy', - 'isSatelliteProvider', - 'isTorExitNode', - 'mobileCountryCode', - 'mobileNetworkCode', - 'network', - 'organization', - 'staticIpScore', - 'userCount', - 'userType', - ]; + public readonly ?int $autonomousSystemNumber; - public function __construct(?array $record) + /** + * @var string|null The organization + * associated with the registered autonomous system number for the IP address. + * See https://en.wikipedia.org/wiki/Autonomous_system_(Internet%29. This + * attribute is only available from the City Plus and Insights web services and + * the GeoIP2 Enterprise database. + */ + public readonly ?string $autonomousSystemOrganization; + + /** + * @var string|null The connection type may take the + * following values: "Dialup", "Cable/DSL", "Corporate", "Cellular", and + * "Satellite". Additional values may be added in the future. This attribute is + * only available from the City Plus and Insights web services and the GeoIP2 + * Enterprise database. + */ + public readonly ?string $connectionType; + + /** + * @var string|null The second level domain associated with the + * IP address. This will be something like "example.com" or "example.co.uk", + * not "foo.example.com". This attribute is only available from the + * City Plus and Insights web services and the GeoIP2 Enterprise + * database. + */ + public readonly ?string $domain; + + /** + * @var string|null The IP address that the data in the model + * is for. If you performed a "me" lookup against the web service, this + * will be the externally routable IP address for the system the code is + * running on. If the system is behind a NAT, this may differ from the IP + * address locally assigned to it. This attribute is returned by all end + * points. + */ + public readonly ?string $ipAddress; + + /** + * @var bool This is true if the IP address belongs to + * any sort of anonymous network. This property is only available from GeoIP2 + * Insights. + */ + public readonly bool $isAnonymous; + + /** + * @var bool This is true if the IP address is + * registered to an anonymous VPN provider. If a VPN provider does not register + * subnets under names associated with them, we will likely only flag their IP + * ranges using the isHostingProvider property. This property is only available + * from GeoIP2 Insights. + */ + public readonly bool $isAnonymousVpn; + + /** + * @var bool This is true if the IP address belongs to an [anycast + * network](https://en.wikipedia.org/wiki/Anycast). This property is not + * available from GeoLite databases or web services. + */ + public readonly bool $isAnycast; + + /** + * @var bool This is true if the IP address belongs + * to a hosting or VPN provider (see description of isAnonymousVpn property). + * This property is only available from GeoIP2 Insights. + */ + public readonly bool $isHostingProvider; + + /** + * @var bool This attribute is true if MaxMind + * believes this IP address to be a legitimate proxy, such as an internal + * VPN used by a corporation. This attribute is only available in the GeoIP2 + * Enterprise database. + */ + public readonly bool $isLegitimateProxy; + + /** + * @var bool This is true if the IP address belongs to + * a public proxy. This property is only available from GeoIP2 Insights. + */ + public readonly bool $isPublicProxy; + + /** + * @var bool This is true if the IP address is + * on a suspected anonymizing network and belongs to a residential ISP. This + * property is only available from GeoIP2 Insights. + */ + public readonly bool $isResidentialProxy; + + /** + * @var bool This is true if the IP address is a Tor + * exit node. This property is only available from GeoIP2 Insights. + */ + public readonly bool $isTorExitNode; + + /** + * @var string|null The name of the ISP associated with the IP + * address. This attribute is only available from the City Plus and Insights + * web services and the GeoIP2 Enterprise database. + */ + public readonly ?string $isp; + + /** + * @var string|null The [mobile country code + * (MCC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with + * the IP address and ISP. This property is available from the City Plus and + * Insights web services and the GeoIP2 Enterprise database. + */ + public readonly ?string $mobileCountryCode; + + /** + * @var string|null The [mobile network code + * (MNC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with + * the IP address and ISP. This property is available from the City Plus and + * Insights web services and the GeoIP2 Enterprise database. + */ + public readonly ?string $mobileNetworkCode; + + /** + * @var string|null The network in CIDR notation associated with + * the record. In particular, this is the largest network where all of the + * fields besides $ipAddress have the same value. + */ + public readonly ?string $network; + + /** + * @var string|null The name of the organization + * associated with the IP address. This attribute is only available from the + * City Plus and Insights web services and the GeoIP2 Enterprise database. + */ + public readonly ?string $organization; + + /** + * @var float|null An indicator of how static or + * dynamic an IP address is. This property is only available from GeoIP2 + * Insights. + */ + public readonly ?float $staticIpScore; + + /** + * @var int|null The estimated number of users sharing + * the IP/network during the past 24 hours. For IPv4, the count is for the + * individual IP. For IPv6, the count is for the /64 network. This property is + * only available from GeoIP2 Insights. + */ + public readonly ?int $userCount; + + /** + * @var string|null

The user type associated with the IP + * address. This can be one of the following values:

+ *
    + *
  • business + *
  • cafe + *
  • cellular + *
  • college + *
  • consumer_privacy_network + *
  • content_delivery_network + *
  • dialup + *
  • government + *
  • hosting + *
  • library + *
  • military + *
  • residential + *
  • router + *
  • school + *
  • search_engine_spider + *
  • traveler + *
+ *

+ * This attribute is only available from the Insights web service and the + * GeoIP2 Enterprise database. + *

+ */ + public readonly ?string $userType; + + public function __construct(array $record) { - if (!isset($record['network']) && isset($record['ip_address'], $record['prefix_len'])) { - $record['network'] = Util::cidr($record['ip_address'], $record['prefix_len']); + $this->autonomousSystemNumber = $record['autonomous_system_number'] ?? null; + $this->autonomousSystemOrganization = $record['autonomous_system_organization'] ?? null; + $this->connectionType = $record['connection_type'] ?? null; + $this->domain = $record['domain'] ?? null; + $this->ipAddress = $record['ip_address'] ?? null; + $this->isAnonymous = $record['is_anonymous'] ?? false; + $this->isAnonymousVpn = $record['is_anonymous_vpn'] ?? false; + $this->isAnycast = $record['is_anycast'] ?? false; + $this->isHostingProvider = $record['is_hosting_provider'] ?? false; + $this->isLegitimateProxy = $record['is_legitimate_proxy'] ?? false; + $this->isp = $record['isp'] ?? null; + $this->isPublicProxy = $record['is_public_proxy'] ?? false; + $this->isResidentialProxy = $record['is_residential_proxy'] ?? false; + $this->isTorExitNode = $record['is_tor_exit_node'] ?? false; + $this->mobileCountryCode = $record['mobile_country_code'] ?? null; + $this->mobileNetworkCode = $record['mobile_network_code'] ?? null; + $this->organization = $record['organization'] ?? null; + $this->staticIpScore = $record['static_ip_score'] ?? null; + $this->userCount = $record['user_count'] ?? null; + $this->userType = $record['user_type'] ?? null; + + if (isset($record['network'])) { + $this->network = $record['network']; + } else { + $this->network = isset($record['prefix_len']) ? Util::cidr($this->ipAddress, $record['prefix_len']) : null; + } + } + + public function jsonSerialize(): array + { + $js = []; + if ($this->autonomousSystemNumber !== null) { + $js['autonomous_system_number'] = $this->autonomousSystemNumber; + } + if ($this->autonomousSystemOrganization !== null) { + $js['autonomous_system_organization'] = $this->autonomousSystemOrganization; + } + if ($this->connectionType !== null) { + $js['connection_type'] = $this->connectionType; + } + if ($this->domain !== null) { + $js['domain'] = $this->domain; + } + if ($this->ipAddress !== null) { + $js['ip_address'] = $this->ipAddress; + } + if ($this->isAnonymous !== false) { + $js['is_anonymous'] = $this->isAnonymous; + } + if ($this->isAnonymousVpn !== false) { + $js['is_anonymous_vpn'] = $this->isAnonymousVpn; + } + if ($this->isAnycast !== false) { + $js['is_anycast'] = $this->isAnycast; + } + if ($this->isHostingProvider !== false) { + $js['is_hosting_provider'] = $this->isHostingProvider; + } + if ($this->isLegitimateProxy !== false) { + $js['is_legitimate_proxy'] = $this->isLegitimateProxy; + } + if ($this->isPublicProxy !== false) { + $js['is_public_proxy'] = $this->isPublicProxy; + } + if ($this->isResidentialProxy !== false) { + $js['is_residential_proxy'] = $this->isResidentialProxy; + } + if ($this->isTorExitNode !== false) { + $js['is_tor_exit_node'] = $this->isTorExitNode; + } + if ($this->isp !== null) { + $js['isp'] = $this->isp; + } + if ($this->mobileCountryCode !== null) { + $js['mobile_country_code'] = $this->mobileCountryCode; + } + if ($this->mobileNetworkCode !== null) { + $js['mobile_network_code'] = $this->mobileNetworkCode; + } + if ($this->network !== null) { + $js['network'] = $this->network; + } + if ($this->organization !== null) { + $js['organization'] = $this->organization; + } + if ($this->staticIpScore !== null) { + $js['static_ip_score'] = $this->staticIpScore; + } + if ($this->userCount !== null) { + $js['user_count'] = $this->userCount; + } + if ($this->userType !== null) { + $js['user_type'] = $this->userType; } - parent::__construct($record); + return $js; } } diff --git a/lib/maxmind/GeoIp2/Util.php b/lib/maxmind/GeoIp2/Util.php index e0a03be8c64..79e813aaf8a 100644 --- a/lib/maxmind/GeoIp2/Util.php +++ b/lib/maxmind/GeoIp2/Util.php @@ -11,6 +11,7 @@ class Util * length. This is for internal use only. * * @internal + * * @ignore */ public static function cidr(string $ipAddress, int $prefixLen): string diff --git a/lib/maxmind/GeoIp2/WebService/Client.php b/lib/maxmind/GeoIp2/WebService/Client.php index 18d979dc617..1e0954195b5 100644 --- a/lib/maxmind/GeoIp2/WebService/Client.php +++ b/lib/maxmind/GeoIp2/WebService/Client.php @@ -51,19 +51,11 @@ class Client implements ProviderInterface /** * @var array */ - private $locales; + private array $locales; + private WsClient $client; + private static string $basePath = '/geoip/v2.1'; - /** - * @var WsClient - */ - private $client; - - /** - * @var string - */ - private static $basePath = '/geoip/v2.1'; - - public const VERSION = 'v2.13.0'; + public const VERSION = 'v3.0.0'; /** * Constructor. @@ -76,7 +68,12 @@ class Client implements ProviderInterface * * `host` - The host to use when querying the web * service. To query the GeoLite2 web service * instead of the GeoIP2 web service, set the - * host to `geolite.info`. + * host to `geolite.info`. To query the Sandbox + * GeoIP2 web service instead of the production + * GeoIP2 web service, set the host to + * `sandbox.maxmind.com`. The sandbox allows you to + * experiment with the API without affecting your + * production data. * * `timeout` - Timeout in seconds. * * `connectTimeout` - Initial connection timeout in seconds. * * `proxy` - The HTTP proxy to use. May include a schema, port, @@ -135,6 +132,8 @@ class Client implements ProviderInterface * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent * class to the above exceptions. It will be thrown directly * if a 200 status code is returned but the body is invalid. + * @throws \InvalidArgumentException if something other than a single IP address or "me" is + * passed to the method */ public function city(string $ipAddress = 'me'): City { @@ -165,6 +164,8 @@ class Client implements ProviderInterface * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent class to the above exceptions. It * will be thrown directly if a 200 status code is returned but * the body is invalid. + * @throws \InvalidArgumentException if something other than a single IP address or "me" is + * passed to the method */ public function country(string $ipAddress = 'me'): Country { @@ -195,6 +196,8 @@ class Client implements ProviderInterface * @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent * class to the above exceptions. It will be thrown directly * if a 200 status code is returned but the body is invalid. + * @throws \InvalidArgumentException if something other than a single IP address or "me" is + * passed to the method */ public function insights(string $ipAddress = 'me'): Insights { @@ -204,6 +207,11 @@ class Client implements ProviderInterface private function responseFor(string $endpoint, string $class, string $ipAddress): Country { + if ($ipAddress !== 'me' && !filter_var($ipAddress, \FILTER_VALIDATE_IP)) { + throw new \InvalidArgumentException( + "The value \"$ipAddress\" is not a valid IP address." + ); + } $path = implode('/', [self::$basePath, $endpoint, $ipAddress]); try { diff --git a/lib/maxmind/GeoIp2/composer.json b/lib/maxmind/GeoIp2/composer.json new file mode 100644 index 00000000000..60908c7d09b --- /dev/null +++ b/lib/maxmind/GeoIp2/composer.json @@ -0,0 +1,32 @@ +{ + "name": "geoip2/geoip2", + "description": "MaxMind GeoIP2 PHP API", + "keywords": ["geoip", "geoip2", "geolocation", "ip", "maxmind"], + "homepage": "https://github.com/maxmind/GeoIP2-php", + "type": "library", + "license": "Apache-2.0", + "authors": [ + { + "name": "Gregory J. Oschwald", + "email": "goschwald@maxmind.com", + "homepage": "https://www.maxmind.com/" + } + ], + "require": { + "maxmind-db/reader": "^1.11.1", + "maxmind/web-service-common": "~0.8", + "php": ">=8.1", + "ext-json": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "3.*", + "phpunit/phpunit": "^10.0", + "squizlabs/php_codesniffer": "3.*", + "phpstan/phpstan": "*" + }, + "autoload": { + "psr-4": { + "GeoIp2\\": "src" + } + } +} diff --git a/lib/maxmind/MaxMind/CHANGELOG.md b/lib/maxmind/MaxMind/CHANGELOG.md index 4c89f0cc982..15e5361797b 100644 --- a/lib/maxmind/MaxMind/CHANGELOG.md +++ b/lib/maxmind/MaxMind/CHANGELOG.md @@ -1,7 +1,14 @@ CHANGELOG ========= -1.11.0 +1.11.1 (2023-12-01) +------------------- + +* Resolve warnings when compiling the C extension. +* Fix various type issues detected by PHPStan level. Pull request by + LauraTaylorUK. GitHub #160. + +1.11.0 (2021-10-18) ------------------- * Replace runtime define of a constant to facilitate opcache preloading. diff --git a/lib/maxmind/MaxMind/Db/Reader.php b/lib/maxmind/MaxMind/Db/Reader.php index 4dfaef44221..1f35e7a4f94 100644 --- a/lib/maxmind/MaxMind/Db/Reader.php +++ b/lib/maxmind/MaxMind/Db/Reader.php @@ -4,15 +4,10 @@ declare(strict_types=1); namespace MaxMind\Db; -use ArgumentCountError; -use BadMethodCallException; -use Exception; -use InvalidArgumentException; use MaxMind\Db\Reader\Decoder; use MaxMind\Db\Reader\InvalidDatabaseException; use MaxMind\Db\Reader\Metadata; use MaxMind\Db\Reader\Util; -use UnexpectedValueException; /** * Instances of this class provide a reader for the MaxMind DB format. IP @@ -24,14 +19,17 @@ class Reader * @var int */ private static $DATA_SECTION_SEPARATOR_SIZE = 16; + /** * @var string */ private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com"; + /** - * @var int + * @var int<0, max> */ private static $METADATA_START_MARKER_LENGTH = 14; + /** * @var int */ @@ -41,18 +39,22 @@ class Reader * @var Decoder */ private $decoder; + /** * @var resource */ private $fileHandle; + /** * @var int */ private $fileSize; + /** * @var int */ private $ipV4Start; + /** * @var Metadata */ @@ -65,22 +67,22 @@ class Reader * @param string $database * the MaxMind DB file to use * - * @throws InvalidArgumentException for invalid database path or unknown arguments + * @throws \InvalidArgumentException for invalid database path or unknown arguments * @throws InvalidDatabaseException - * if the database is invalid or there is an error reading - * from it + * if the database is invalid or there is an error reading + * from it */ public function __construct(string $database) { if (\func_num_args() !== 1) { - throw new ArgumentCountError( + throw new \ArgumentCountError( sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args()) ); } $fileHandle = @fopen($database, 'rb'); if ($fileHandle === false) { - throw new InvalidArgumentException( + throw new \InvalidArgumentException( "The file \"$database\" does not exist or is not readable." ); } @@ -88,7 +90,7 @@ class Reader $fileSize = @filesize($database); if ($fileSize === false) { - throw new UnexpectedValueException( + throw new \UnexpectedValueException( "Error determining the size of \"$database\"." ); } @@ -111,18 +113,18 @@ class Reader * @param string $ipAddress * the IP address to look up * - * @throws BadMethodCallException if this method is called on a closed database - * @throws InvalidArgumentException if something other than a single IP address is passed to the method + * @throws \BadMethodCallException if this method is called on a closed database + * @throws \InvalidArgumentException if something other than a single IP address is passed to the method * @throws InvalidDatabaseException - * if the database is invalid or there is an error reading - * from it + * if the database is invalid or there is an error reading + * from it * * @return mixed the record for the IP address */ public function get(string $ipAddress) { if (\func_num_args() !== 1) { - throw new ArgumentCountError( + throw new \ArgumentCountError( sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args()) ); } @@ -137,11 +139,11 @@ class Reader * @param string $ipAddress * the IP address to look up * - * @throws BadMethodCallException if this method is called on a closed database - * @throws InvalidArgumentException if something other than a single IP address is passed to the method + * @throws \BadMethodCallException if this method is called on a closed database + * @throws \InvalidArgumentException if something other than a single IP address is passed to the method * @throws InvalidDatabaseException - * if the database is invalid or there is an error reading - * from it + * if the database is invalid or there is an error reading + * from it * * @return array an array where the first element is the record and the * second the network prefix length for the record @@ -149,13 +151,13 @@ class Reader public function getWithPrefixLen(string $ipAddress): array { if (\func_num_args() !== 1) { - throw new ArgumentCountError( + throw new \ArgumentCountError( sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args()) ); } if (!\is_resource($this->fileHandle)) { - throw new BadMethodCallException( + throw new \BadMethodCallException( 'Attempt to read from a closed MaxMind DB.' ); } @@ -172,12 +174,17 @@ class Reader { $packedAddr = @inet_pton($ipAddress); if ($packedAddr === false) { - throw new InvalidArgumentException( + throw new \InvalidArgumentException( "The value \"$ipAddress\" is not a valid IP address." ); } $rawAddress = unpack('C*', $packedAddr); + if ($rawAddress === false) { + throw new InvalidDatabaseException( + 'Could not unpack the unsigned char of the packed in_addr representation.' + ); + } $bitCount = \count($rawAddress) * 8; @@ -194,7 +201,7 @@ class Reader $node = $this->ipV4Start; } } elseif ($metadata->ipVersion === 4 && $bitCount === 128) { - throw new InvalidArgumentException( + throw new \InvalidArgumentException( "Error looking up $ipAddress. You attempted to look up an" . ' IPv6 address in an IPv4-only database.' ); @@ -245,7 +252,13 @@ class Reader switch ($this->metadata->recordSize) { case 24: $bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3); - [, $node] = unpack('N', "\x00" . $bytes); + $rc = unpack('N', "\x00" . $bytes); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack the unsigned long of the node.' + ); + } + [, $node] = $rc; return $node; @@ -256,13 +269,25 @@ class Reader } else { $middle = 0x0F & \ord($bytes[0]); } - [, $node] = unpack('N', \chr($middle) . substr($bytes, $index, 3)); + $rc = unpack('N', \chr($middle) . substr($bytes, $index, 3)); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack the unsigned long of the node.' + ); + } + [, $node] = $rc; return $node; case 32: $bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4); - [, $node] = unpack('N', $bytes); + $rc = unpack('N', $bytes); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack the unsigned long of the node.' + ); + } + [, $node] = $rc; return $node; @@ -301,6 +326,11 @@ class Reader { $handle = $this->fileHandle; $fstat = fstat($handle); + if ($fstat === false) { + throw new InvalidDatabaseException( + "Error getting file information ($filename)." + ); + } $fileSize = $fstat['size']; $marker = self::$METADATA_START_MARKER; $markerLength = self::$METADATA_START_MARKER_LENGTH; @@ -325,15 +355,15 @@ class Reader } /** - * @throws InvalidArgumentException if arguments are passed to the method - * @throws BadMethodCallException if the database has been closed + * @throws \InvalidArgumentException if arguments are passed to the method + * @throws \BadMethodCallException if the database has been closed * * @return Metadata object for the database */ public function metadata(): Metadata { if (\func_num_args()) { - throw new ArgumentCountError( + throw new \ArgumentCountError( sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args()) ); } @@ -341,7 +371,7 @@ class Reader // Not technically required, but this makes it consistent with // C extension and it allows us to change our implementation later. if (!\is_resource($this->fileHandle)) { - throw new BadMethodCallException( + throw new \BadMethodCallException( 'Attempt to read from a closed MaxMind DB.' ); } @@ -352,19 +382,19 @@ class Reader /** * Closes the MaxMind DB and returns resources to the system. * - * @throws Exception - * if an I/O error occurs + * @throws \Exception + * if an I/O error occurs */ public function close(): void { if (\func_num_args()) { - throw new ArgumentCountError( + throw new \ArgumentCountError( sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args()) ); } if (!\is_resource($this->fileHandle)) { - throw new BadMethodCallException( + throw new \BadMethodCallException( 'Attempt to close a closed MaxMind DB.' ); } diff --git a/lib/maxmind/MaxMind/Db/Reader/Decoder.php b/lib/maxmind/MaxMind/Db/Reader/Decoder.php index babaed878c7..44eee622ab1 100644 --- a/lib/maxmind/MaxMind/Db/Reader/Decoder.php +++ b/lib/maxmind/MaxMind/Db/Reader/Decoder.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace MaxMind\Db\Reader; // @codingStandardsIgnoreLine -use RuntimeException; class Decoder { @@ -13,20 +12,19 @@ class Decoder * @var resource */ private $fileStream; + /** * @var int */ private $pointerBase; - /** - * @var float - */ - private $pointerBaseByteSize; + /** * This is only used for unit testing. * * @var bool */ private $pointerTestHack; + /** * @var bool */ @@ -44,8 +42,8 @@ class Decoder private const _UINT64 = 9; private const _UINT128 = 10; private const _ARRAY = 11; - private const _CONTAINER = 12; - private const _END_MARKER = 13; + // 12 is the container type + // 13 is the end marker type private const _BOOLEAN = 14; private const _FLOAT = 15; @@ -60,7 +58,6 @@ class Decoder $this->fileStream = $fileStream; $this->pointerBase = $pointerBase; - $this->pointerBaseByteSize = $pointerBase > 0 ? log($pointerBase, 2) / 8 : 0; $this->pointerTestHack = $pointerTestHack; $this->switchByteOrder = $this->isPlatformLittleEndian(); @@ -111,6 +108,9 @@ class Decoder return $this->decodeByType($type, $offset, $size); } + /** + * @param int<0, max> $size + */ private function decodeByType(int $type, int $offset, int $size): array { switch ($type) { @@ -188,7 +188,13 @@ class Decoder { // This assumes IEEE 754 doubles, but most (all?) modern platforms // use them. - [, $double] = unpack('E', $bytes); + $rc = unpack('E', $bytes); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack a double value from the given bytes.' + ); + } + [, $double] = $rc; return $double; } @@ -197,7 +203,13 @@ class Decoder { // This assumes IEEE 754 floats, but most (all?) modern platforms // use them. - [, $float] = unpack('G', $bytes); + $rc = unpack('G', $bytes); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack a float value from the given bytes.' + ); + } + [, $float] = $rc; return $float; } @@ -224,7 +236,13 @@ class Decoder ); } - [, $int] = unpack('l', $this->maybeSwitchByteOrder($bytes)); + $rc = unpack('l', $this->maybeSwitchByteOrder($bytes)); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack a 32bit integer value from the given bytes.' + ); + } + [, $int] = $rc; return $int; } @@ -247,19 +265,31 @@ class Decoder $pointerSize = (($ctrlByte >> 3) & 0x3) + 1; $buffer = Util::read($this->fileStream, $offset, $pointerSize); - $offset = $offset + $pointerSize; + $offset += $pointerSize; switch ($pointerSize) { case 1: $packed = \chr($ctrlByte & 0x7) . $buffer; - [, $pointer] = unpack('n', $packed); + $rc = unpack('n', $packed); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack an unsigned short value from the given bytes (pointerSize is 1).' + ); + } + [, $pointer] = $rc; $pointer += $this->pointerBase; break; case 2: $packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer; - [, $pointer] = unpack('N', $packed); + $rc = unpack('N', $packed); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack an unsigned long value from the given bytes (pointerSize is 2).' + ); + } + [, $pointer] = $rc; $pointer += $this->pointerBase + 2048; break; @@ -269,7 +299,13 @@ class Decoder // It is safe to use 'N' here, even on 32 bit machines as the // first bit is 0. - [, $pointer] = unpack('N', $packed); + $rc = unpack('N', $packed); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack an unsigned long value from the given bytes (pointerSize is 3).' + ); + } + [, $pointer] = $rc; $pointer += $this->pointerBase + 526336; break; @@ -284,7 +320,7 @@ class Decoder if (\PHP_INT_MAX - $pointerBase >= $pointerOffset) { $pointer = $pointerOffset + $pointerBase; } else { - throw new RuntimeException( + throw new \RuntimeException( 'The database offset is too large to be represented on your platform.' ); } @@ -307,32 +343,39 @@ class Decoder return 0; } - $integer = 0; - // PHP integers are signed. PHP_INT_SIZE - 1 is the number of // complete bytes that can be converted to an integer. However, // we can convert another byte if the leading bit is zero. $useRealInts = $byteLength <= \PHP_INT_SIZE - 1 || ($byteLength === \PHP_INT_SIZE && (\ord($bytes[0]) & 0x80) === 0); + if ($useRealInts) { + $integer = 0; + for ($i = 0; $i < $byteLength; ++$i) { + $part = \ord($bytes[$i]); + $integer = ($integer << 8) + $part; + } + + return $integer; + } + + // We only use gmp or bcmath if the final value is too big + $integerAsString = '0'; for ($i = 0; $i < $byteLength; ++$i) { $part = \ord($bytes[$i]); - // We only use gmp or bcmath if the final value is too big - if ($useRealInts) { - $integer = ($integer << 8) + $part; - } elseif (\extension_loaded('gmp')) { - $integer = gmp_strval(gmp_add(gmp_mul((string) $integer, '256'), $part)); + if (\extension_loaded('gmp')) { + $integerAsString = gmp_strval(gmp_add(gmp_mul($integerAsString, '256'), $part)); } elseif (\extension_loaded('bcmath')) { - $integer = bcadd(bcmul((string) $integer, '256'), (string) $part); + $integerAsString = bcadd(bcmul($integerAsString, '256'), (string) $part); } else { - throw new RuntimeException( + throw new \RuntimeException( 'The gmp or bcmath extension must be installed to read this database.' ); } } - return $integer; + return $integerAsString; } private function sizeFromCtrlByte(int $ctrlByte, int $offset): array @@ -349,10 +392,22 @@ class Decoder if ($size === 29) { $size = 29 + \ord($bytes); } elseif ($size === 30) { - [, $adjust] = unpack('n', $bytes); + $rc = unpack('n', $bytes); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack an unsigned short value from the given bytes.' + ); + } + [, $adjust] = $rc; $size = 285 + $adjust; } else { - [, $adjust] = unpack('N', "\x00" . $bytes); + $rc = unpack('N', "\x00" . $bytes); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack an unsigned long value from the given bytes.' + ); + } + [, $adjust] = $rc; $size = $adjust + 65821; } @@ -368,7 +423,13 @@ class Decoder { $testint = 0x00FF; $packed = pack('S', $testint); + $rc = unpack('v', $packed); + if ($rc === false) { + throw new InvalidDatabaseException( + 'Could not unpack an unsigned short value from the given bytes.' + ); + } - return $testint === current(unpack('v', $packed)); + return $testint === current($rc); } } diff --git a/lib/maxmind/MaxMind/Db/Reader/InvalidDatabaseException.php b/lib/maxmind/MaxMind/Db/Reader/InvalidDatabaseException.php index 53231070329..b1da1ed247c 100644 --- a/lib/maxmind/MaxMind/Db/Reader/InvalidDatabaseException.php +++ b/lib/maxmind/MaxMind/Db/Reader/InvalidDatabaseException.php @@ -4,11 +4,8 @@ declare(strict_types=1); namespace MaxMind\Db\Reader; -use Exception; - /** * This class should be thrown when unexpected data is found in the database. */ -class InvalidDatabaseException extends Exception -{ -} +// phpcs:disable +class InvalidDatabaseException extends \Exception {} diff --git a/lib/maxmind/MaxMind/Db/Reader/Metadata.php b/lib/maxmind/MaxMind/Db/Reader/Metadata.php index 873064b0a20..97ef48821c4 100644 --- a/lib/maxmind/MaxMind/Db/Reader/Metadata.php +++ b/lib/maxmind/MaxMind/Db/Reader/Metadata.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace MaxMind\Db\Reader; -use ArgumentCountError; - /** * This class provides the metadata for the MaxMind DB file. */ @@ -18,6 +16,7 @@ class Metadata * @var int */ public $binaryFormatMajorVersion; + /** * This is an unsigned 16-bit integer indicating the minor version number * for the database's binary format. @@ -25,6 +24,7 @@ class Metadata * @var int */ public $binaryFormatMinorVersion; + /** * This is an unsigned 64-bit integer that contains the database build * timestamp as a Unix epoch value. @@ -32,6 +32,7 @@ class Metadata * @var int */ public $buildEpoch; + /** * This is a string that indicates the structure of each data record * associated with an IP address. The actual definition of these @@ -40,6 +41,7 @@ class Metadata * @var string */ public $databaseType; + /** * This key will always point to a map (associative array). The keys of * that map will be language codes, and the values will be a description @@ -49,6 +51,7 @@ class Metadata * @var array */ public $description; + /** * This is an unsigned 16-bit integer which is always 4 or 6. It indicates * whether the database contains IPv4 or IPv6 address data. @@ -56,6 +59,7 @@ class Metadata * @var int */ public $ipVersion; + /** * An array of strings, each of which is a language code. A given record * may contain data items that have been localized to some or all of @@ -64,10 +68,12 @@ class Metadata * @var array */ public $languages; + /** * @var int */ public $nodeByteSize; + /** * This is an unsigned 32-bit integer indicating the number of nodes in * the search tree. @@ -75,6 +81,7 @@ class Metadata * @var int */ public $nodeCount; + /** * This is an unsigned 16-bit integer. It indicates the number of bits in a * record in the search tree. Note that each node consists of two records. @@ -82,6 +89,7 @@ class Metadata * @var int */ public $recordSize; + /** * @var int */ @@ -90,7 +98,7 @@ class Metadata public function __construct(array $metadata) { if (\func_num_args() !== 1) { - throw new ArgumentCountError( + throw new \ArgumentCountError( sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args()) ); } diff --git a/lib/maxmind/MaxMind/Db/Reader/Util.php b/lib/maxmind/MaxMind/Db/Reader/Util.php index 89db715e3bc..c2c3212d9cf 100644 --- a/lib/maxmind/MaxMind/Db/Reader/Util.php +++ b/lib/maxmind/MaxMind/Db/Reader/Util.php @@ -7,7 +7,8 @@ namespace MaxMind\Db\Reader; class Util { /** - * @param resource $stream + * @param resource $stream + * @param int<0, max> $numberOfBytes */ public static function read($stream, int $offset, int $numberOfBytes): string { diff --git a/lib/maxmind/MaxMind/README.md b/lib/maxmind/MaxMind/README.md index 49fa280019e..fa5693a2968 100644 --- a/lib/maxmind/MaxMind/README.md +++ b/lib/maxmind/MaxMind/README.md @@ -132,7 +132,7 @@ make test sudo make install ``` -You then must load your extension. The recommend method is to add the +You then must load your extension. The recommended method is to add the following to your `php.ini` file: ``` @@ -180,6 +180,6 @@ The MaxMind DB Reader PHP API uses [Semantic Versioning](https://semver.org/). ## Copyright and License ## -This software is Copyright (c) 2014-2020 by MaxMind, Inc. +This software is Copyright (c) 2014-2023 by MaxMind, Inc. This is free software, licensed under the Apache License, Version 2.0. diff --git a/lib/maxmind/MaxMind/autoload.php b/lib/maxmind/MaxMind/autoload.php index a1119e7f85d..ffaf01b2275 100644 --- a/lib/maxmind/MaxMind/autoload.php +++ b/lib/maxmind/MaxMind/autoload.php @@ -35,7 +35,7 @@ function mmdb_autoload($class): void $path = str_replace('\\', '/', $path); // and finally, add the PHP file extension to the result. - $path = $path . '.php'; + $path .= '.php'; // $path should now contain the path to a PHP file defining $class if (file_exists($path)) { diff --git a/lib/maxmind/MaxMind/composer.json b/lib/maxmind/MaxMind/composer.json index 26d48730125..22ea7975230 100644 --- a/lib/maxmind/MaxMind/composer.json +++ b/lib/maxmind/MaxMind/composer.json @@ -21,7 +21,7 @@ "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" }, "conflict": { - "ext-maxminddb": "<1.10.1,>=2.0.0" + "ext-maxminddb": "<1.11.1,>=2.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", diff --git a/lib/maxmind/readme_moodle.txt b/lib/maxmind/readme_moodle.txt index 7887e5cd45e..2f25ec86ce0 100644 --- a/lib/maxmind/readme_moodle.txt +++ b/lib/maxmind/readme_moodle.txt @@ -28,6 +28,11 @@ mv GeoIP2-php-X.Y.Z/src/ /path/to/moodle/lib/maxmind/GeoIp2/ mv MaxMind-DB-Reader-php-X.Y.Z/src/MaxMind/ /path/to/moodle/lib/maxmind/MaxMind/ 4) Update other MaxMind related files: +mv GeoIP2-php-X.Y.Z/CHANGELOG.md /path/to/moodle/lib/maxmind/GeoIp2/ +mv GeoIP2-php-X.Y.Z/README.md /path/to/moodle/lib/maxmind/GeoIp2/ +mv GeoIP2-php-X.Y.Z/composer.json /path/to/moodle/lib/maxmind/GeoIp2/ +mv GeoIP2-php-X.Y.Z/LICENSE /path/to/moodle/lib/maxmind/GeoIp2/ + mv MaxMind-DB-Reader-php-X.Y.Z/LICENSE /path/to/moodle/lib/maxmind/MaxMind/ mv MaxMind-DB-Reader-php-X.Y.Z/CHANGELOG.md /path/to/moodle/lib/maxmind/MaxMind/ mv MaxMind-DB-Reader-php-X.Y.Z/README.md /path/to/moodle/lib/maxmind/MaxMind/ diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index 2f877b1c774..b487672e212 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -388,7 +388,7 @@ All rights reserved. maxmind/GeoIp2 GeoIP2 PHP API Library for processing of GeoIP data files. - 2.13.0 + 3.0.0 Apache 2.0 https://github.com/maxmind/GeoIP2-php @@ -400,7 +400,7 @@ All rights reserved. maxmind/MaxMind MaxMind DB Reader API PHP API for reading MaxMind DB files. - 1.11.0 + 1.11.1 Apache 2.0 https://github.com/maxmind/MaxMind-DB-Reader-php/