Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

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