Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  namespace GeoIp2\Database;
   6  
   7  use GeoIp2\Exception\AddressNotFoundException;
   8  use GeoIp2\ProviderInterface;
   9  use MaxMind\Db\Reader as DbReader;
  10  use MaxMind\Db\Reader\InvalidDatabaseException;
  11  
  12  /**
  13   * Instances of this class provide a reader for the GeoIP2 database format.
  14   * IP addresses can be looked up using the database specific methods.
  15   *
  16   * ## Usage ##
  17   *
  18   * The basic API for this class is the same for every database. First, you
  19   * create a reader object, specifying a file name. You then call the method
  20   * corresponding to the specific database, passing it the IP address you want
  21   * to look up.
  22   *
  23   * If the request succeeds, the method call will return a model class for
  24   * the method you called. This model in turn contains multiple record classes,
  25   * each of which represents part of the data returned by the database. If
  26   * the database does not contain the requested information, the attributes
  27   * on the record class will have a `null` value.
  28   *
  29   * If the address is not in the database, an
  30   * {@link \GeoIp2\Exception\AddressNotFoundException} exception will be
  31   * thrown. If an invalid IP address is passed to one of the methods, a
  32   * SPL {@link \InvalidArgumentException} will be thrown. If the database is
  33   * corrupt or invalid, a {@link \MaxMind\Db\Reader\InvalidDatabaseException}
  34   * will be thrown.
  35   */
  36  class Reader implements ProviderInterface
  37  {
  38      private $dbReader;
  39      private $dbType;
  40      private $locales;
  41  
  42      /**
  43       * Constructor.
  44       *
  45       * @param string $filename the path to the GeoIP2 database file
  46       * @param array  $locales  list of locale codes to use in name property
  47       *                         from most preferred to least preferred
  48       *
  49       * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
  50       *                                                     is corrupt or invalid
  51       */
  52      public function __construct(
  53          string $filename,
  54          array $locales = ['en']
  55      ) {
  56          $this->dbReader = new DbReader($filename);
  57          $this->dbType = $this->dbReader->metadata()->databaseType;
  58          $this->locales = $locales;
  59      }
  60  
  61      /**
  62       * This method returns a GeoIP2 City model.
  63       *
  64       * @param string $ipAddress an IPv4 or IPv6 address as a string
  65       *
  66       * @throws \GeoIp2\Exception\AddressNotFoundException  if the address is
  67       *                                                     not in the database
  68       * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
  69       *                                                     is corrupt or invalid
  70       */
  71      public function city(string $ipAddress): \GeoIp2\Model\City
  72      {
  73          return $this->modelFor('City', 'City', $ipAddress);
  74      }
  75  
  76      /**
  77       * This method returns a GeoIP2 Country model.
  78       *
  79       * @param string $ipAddress an IPv4 or IPv6 address as a string
  80       *
  81       * @throws \GeoIp2\Exception\AddressNotFoundException  if the address is
  82       *                                                     not in the database
  83       * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
  84       *                                                     is corrupt or invalid
  85       */
  86      public function country(string $ipAddress): \GeoIp2\Model\Country
  87      {
  88          return $this->modelFor('Country', 'Country', $ipAddress);
  89      }
  90  
  91      /**
  92       * This method returns a GeoIP2 Anonymous IP model.
  93       *
  94       * @param string $ipAddress an IPv4 or IPv6 address as a string
  95       *
  96       * @throws \GeoIp2\Exception\AddressNotFoundException  if the address is
  97       *                                                     not in the database
  98       * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
  99       *                                                     is corrupt or invalid
 100       */
 101      public function anonymousIp(string $ipAddress): \GeoIp2\Model\AnonymousIp
 102      {
 103          return $this->flatModelFor(
 104              'AnonymousIp',
 105              'GeoIP2-Anonymous-IP',
 106              $ipAddress
 107          );
 108      }
 109  
 110      /**
 111       * This method returns a GeoLite2 ASN model.
 112       *
 113       * @param string $ipAddress an IPv4 or IPv6 address as a string
 114       *
 115       * @throws \GeoIp2\Exception\AddressNotFoundException  if the address is
 116       *                                                     not in the database
 117       * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
 118       *                                                     is corrupt or invalid
 119       */
 120      public function asn(string $ipAddress): \GeoIp2\Model\Asn
 121      {
 122          return $this->flatModelFor(
 123              'Asn',
 124              'GeoLite2-ASN',
 125              $ipAddress
 126          );
 127      }
 128  
 129      /**
 130       * This method returns a GeoIP2 Connection Type model.
 131       *
 132       * @param string $ipAddress an IPv4 or IPv6 address as a string
 133       *
 134       * @throws \GeoIp2\Exception\AddressNotFoundException  if the address is
 135       *                                                     not in the database
 136       * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
 137       *                                                     is corrupt or invalid
 138       */
 139      public function connectionType(string $ipAddress): \GeoIp2\Model\ConnectionType
 140      {
 141          return $this->flatModelFor(
 142              'ConnectionType',
 143              'GeoIP2-Connection-Type',
 144              $ipAddress
 145          );
 146      }
 147  
 148      /**
 149       * This method returns a GeoIP2 Domain model.
 150       *
 151       * @param string $ipAddress an IPv4 or IPv6 address as a string
 152       *
 153       * @throws \GeoIp2\Exception\AddressNotFoundException  if the address is
 154       *                                                     not in the database
 155       * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
 156       *                                                     is corrupt or invalid
 157       */
 158      public function domain(string $ipAddress): \GeoIp2\Model\Domain
 159      {
 160          return $this->flatModelFor(
 161              'Domain',
 162              'GeoIP2-Domain',
 163              $ipAddress
 164          );
 165      }
 166  
 167      /**
 168       * This method returns a GeoIP2 Enterprise model.
 169       *
 170       * @param string $ipAddress an IPv4 or IPv6 address as a string
 171       *
 172       * @throws \GeoIp2\Exception\AddressNotFoundException  if the address is
 173       *                                                     not in the database
 174       * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
 175       *                                                     is corrupt or invalid
 176       */
 177      public function enterprise(string $ipAddress): \GeoIp2\Model\Enterprise
 178      {
 179          return $this->modelFor('Enterprise', 'Enterprise', $ipAddress);
 180      }
 181  
 182      /**
 183       * This method returns a GeoIP2 ISP model.
 184       *
 185       * @param string $ipAddress an IPv4 or IPv6 address as a string
 186       *
 187       * @throws \GeoIp2\Exception\AddressNotFoundException  if the address is
 188       *                                                     not in the database
 189       * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
 190       *                                                     is corrupt or invalid
 191       */
 192      public function isp(string $ipAddress): \GeoIp2\Model\Isp
 193      {
 194          return $this->flatModelFor(
 195              'Isp',
 196              'GeoIP2-ISP',
 197              $ipAddress
 198          );
 199      }
 200  
 201      private function modelFor(string $class, string $type, string $ipAddress)
 202      {
 203          list($record, $prefixLen) = $this->getRecord($class, $type, $ipAddress);
 204  
 205          $record['traits']['ip_address'] = $ipAddress;
 206          $record['traits']['prefix_len'] = $prefixLen;
 207  
 208          $class = 'GeoIp2\\Model\\' . $class;
 209  
 210          return new $class($record, $this->locales);
 211      }
 212  
 213      private function flatModelFor(string $class, string $type, string $ipAddress)
 214      {
 215          list($record, $prefixLen) = $this->getRecord($class, $type, $ipAddress);
 216  
 217          $record['ip_address'] = $ipAddress;
 218          $record['prefix_len'] = $prefixLen;
 219          $class = 'GeoIp2\\Model\\' . $class;
 220  
 221          return new $class($record);
 222      }
 223  
 224      private function getRecord(string $class, string $type, string $ipAddress): array
 225      {
 226          if (strpos($this->dbType, $type) === false) {
 227              $method = lcfirst($class);
 228              throw new \BadMethodCallException(
 229                  "The $method method cannot be used to open a {$this->dbType} database"
 230              );
 231          }
 232          list($record, $prefixLen) = $this->dbReader->getWithPrefixLen($ipAddress);
 233          if ($record === null) {
 234              throw new AddressNotFoundException(
 235                  "The address $ipAddress is not in the database."
 236              );
 237          }
 238          if (!\is_array($record)) {
 239              // This can happen on corrupt databases. Generally,
 240              // MaxMind\Db\Reader will throw a
 241              // MaxMind\Db\Reader\InvalidDatabaseException, but occasionally
 242              // the lookup may result in a record that looks valid but is not
 243              // an array. This mostly happens when the user is ignoring all
 244              // exceptions and the more frequent InvalidDatabaseException
 245              // exceptions go unnoticed.
 246              throw new InvalidDatabaseException(
 247                  "Expected an array when looking up $ipAddress but received: "
 248                  . \gettype($record)
 249              );
 250          }
 251  
 252          return [$record, $prefixLen];
 253      }
 254  
 255      /**
 256       * @throws \InvalidArgumentException if arguments are passed to the method
 257       * @throws \BadMethodCallException   if the database has been closed
 258       *
 259       * @return \MaxMind\Db\Reader\Metadata object for the database
 260       */
 261      public function metadata(): \MaxMind\Db\Reader\Metadata
 262      {
 263          return $this->dbReader->metadata();
 264      }
 265  
 266      /**
 267       * Closes the GeoIP2 database and returns the resources to the system.
 268       */
 269      public function close(): void
 270      {
 271          $this->dbReader->close();
 272      }
 273  }